[英]Why is std::rotate so fast?
為什么std::rotate
比cplusplus.com描述的等效函數快得多?
cplusplus.com的實施:
template <class ForwardIterator>
void rotate (ForwardIterator first, ForwardIterator middle, ForwardIterator last)
{
ForwardIterator next= middle;
while (first != next)
{
swap (*first++, *next++);
if(next == last)
next= middle;
else if (first==middle)
middle= next;
}
}
我有兩個完全相同的插入排序算法,除了一個使用std::rotate
,一個使用cplusplus.com的等效函數。 我將它們設置為使用1000個int
元素對1000個向量進行排序。 使用std::rotate
的排序需要0.376秒,而另一個需要8.181秒。
為什么是這樣? 我不打算嘗試做出比STL功能更好的東西,但我仍然很好奇。
正如評論員已經說過的那樣,這取決於您的標准庫實施。 但是,即使對於前向迭代器 ,您發布的代碼也是有效的。 因此,它只需要很少的要求(只有這些迭代器可以遞增和解除引用)。
Stepanov的經典編程元素將整個章節(10)用於rotate
和其他重排算法。 對於正向迭代器,代碼中的一系列交換會給出O(3N)
分配。 對於雙向迭代器 ,三個連續的reverse
調用產生另一個O(3N)
算法。 對於隨機訪問迭代器 , std::rotate
可以通過first
為起始迭代器定義索引的排列來實現為O(N)
賦值。
所有上述算法都是就地的。 使用內存緩沖區,隨機訪問版本可能受益於memcpy()
或memmove()
(如果基礎值類型為POD)的更高緩存局部性,其中可以交換整個連續內存塊。 如果您對數組或std::vector
插入排序,則標准庫可能會利用此優化。
TL; DR :信任您的標准庫,不要重新發明輪子!
編輯:
由於沒有給出上下文,不清楚你的代碼是否調用std::swap()
或其他swap(a,b)
算法
T tmp = a; a = b; b = tmp;
當a
和b
各為1000 int
s的向量時,這將復制所有向量元素3次。 像std::vector<T>
的容器的std::swap()
的專用版本調用容器a.swap(b)
方法,實質上只交換容器的動態數據指針。
此外,對於不同的迭代器類型, std::rotate()
實現可以使用一些優化(請參閱下面的舊版,可能誤導性的答案)。
警告: std::rotate()
的實現依賴於實現。 對於不同的迭代器類別,可以使用不同的算法(例如,查找__rotate(
在GNU g ++的bits/stl_algo.h
頭文件中)。
要通過m=std::distance(first,middle)
移動n
元素,像一個元素的m個旋轉這樣的簡單(天真)算法需要O(n * m)個移動或復制操作。 但是,當每個元素直接放置到其正確位置時,只需要O(n)移動,這導致算法的(大約) m倍。
舉例說明:通過三個元素旋轉字符串s = "abcdefg"
:
abcdefg : store 'a' in temporary place
dbcdefg : move s[3] to s[0] (where it belongs in the end, directly)
dbcgefg : move s[6] to s[3]
dbcgefc : move s[9%7] to s[6] (wrapping index modulo container size: 9%7 == 2)
dbfgefc : move s[5] to s[2]
dbfgebc : move s[1] to s[5] (another wrapping around)
defgebc : move s[4] to s[1]
defgabc : move 'a' from temporary place to s[4]
對於具有最大公約數1的n
和m
,您現在就完成了。 否則,您必須為前m
個連續元素重復該方案n/m
時間(此處假設n > m
)。 這個更復雜的算法要快得多。
對於雙向迭代器,可以使用另一種傳奇的O(3n)算法,稱為“翻轉手”。 根據Jon Bentley的書“ Programming Pearls”,它在早期的UNIX編輯器中用於移動文本:
將雙手放在你面前,一個放在另一個上面,豎起大拇指。 現在
在代碼中:
reverse(first, middle);
reverse(middle, last);
reverse(first, last);
對於隨機訪問迭代器 ,可以通過swap_ranges()
(或POD類型的memmove()
操作swap_ranges()
重新定位大塊內存。
通過利用匯編程序操作的微優化可以提供少量額外的加速度,它可以在禁食算法之上完成。
使用連續元素而不是在存儲器中“跳轉”的算法也導致現代計算機體系結構上的較少數量的高速緩存未命中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.