簡體   English   中英

為什么std ::旋轉這么快?

[英]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;

ab各為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的nm ,您現在就完成了。 否則,您必須為前m個連續元素重復該方案n/m時間(此處假設n > m )。 這個更復雜的算法要快得多。

對於雙向迭代器,可以使用另一種傳奇的O(3n)算法,稱為“翻轉手”。 根據Jon Bentley的書“ Programming Pearls”,它在早期的UNIX編輯器中用於移動文本:

將雙手放在你面前,一個放在另一個上面,豎起大拇指。 現在

  1. 轉一只手。
  2. 轉向另一個。
  3. 轉動兩者,相互連接。

在代碼中:

reverse(first, middle);
reverse(middle, last);
reverse(first, last);

對於隨機訪問迭代器 ,可以通過swap_ranges() (或POD類型的memmove()操作swap_ranges()重新定位大塊內存。

通過利用匯編程序操作的微優化可以提供少量額外的加速度,它可以在禁食算法之上完成。

使用連續元素而不是在存儲器中“跳轉”的算法也導致現代計算機體系結構上的較少數量的高速緩存未命中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM