簡體   English   中英

如何繞過循環瓶頸進行恆定時間操作?

[英]How to get around for loop bottleneck for constant time operations?

在規則不可知的撲克模擬器上工作以獲得樂趣。 測試枚舉中的瓶頸,以及總是從“獨特”陣列中獲取的手,我發現了一個有趣的瓶頸。 我測量了運行每個變量低於1,000,000,000次的平均計算時間,然后在100次重復中進行了最佳操作,以允許JIT和Hotspot發揮他們的魔力。 我發現之間的計算時間(6ns vs 27ns)存在差異

public int getRank7(int ... cards) {
  int q = (cards[0] >> 16) | (cards[1] >> 16) | (cards[2] >> 16) | (cards[3] >> 16) | (cards[4] >> 16) | (cards[5] >> 16) | (cards[6] >> 16);
  int product = ((cards[0] & 0xFF) * (cards[1] & 0xFF) * (cards[2] & 0xFF) * (cards[3] & 0xFF) * (cards[4] & 0xFF) * (cards[5] & 0xFF) * (cards[6] & 0xFF));
  if(flushes[q] > 0) return flushes[q];
  if(unique[q] > 0) return unique[q];
  int x = Arrays.binarySearch(products, product);
  return rankings[x];
}

public int getRank(int ... cards) {
  int q = 0;
  long product = 1;
  for(int c : cards) {
    q |= (c >> 16);
    product *= (c & 0xFF);
  }
  if(flushes[q] > 0) return flushes[q];
  if(unique[q] > 0) return unique[q];
  int x = Arrays.binarySearch(products, product);
  return rankings[x];
}

問題肯定是for循環,而不是在函數頂部添加處理乘法。 我對此感到困惑,因為我在每個場景中都運行了相同數量的操作...我意識到我在這個功能中總是有6張或更多卡片所以我通過將它改為

public int getRank(int c0, int c1, int c2, int c3, int c4, int c5, int ... cards)

但是,隨着卡數量的增加,我將面臨同樣的瓶頸。 有沒有辦法解決這個問題,如果沒有,有人可以向我解釋為什么相同數量的操作的for循環要慢得多?

我想你會發現最大的不同就是分支。 for循環場景需要在for循環的每次迭代中進行檢查和條件分支。 你的CPU將嘗試預測將采用哪個分支,並相應地預測管道指令,但是當它錯誤預測時(每個函數調用至少一次,因為循環終止),管道停滯,這是非常昂貴的。

要嘗試的一件事是具有固定上限的常規for循環(而不是基於數組長度的循環); Java JRE可以展開這樣的循環,這將導致與更高效版本相同的操作序列。

增強的for循環需要設置一個迭代器,當你只有少數幾個項目時這是相對昂貴的。

如果你寫了一個傳統的for循環,看看你的時間是多么有趣:

for (int i = 0; i < cards.length; ++i)
{
    q |= (cards[i] >> 16);
    product *= (cards[i] & 0xFF);
}

但即使這可能比第一個例子略慢,因為有一些循環開銷(遞增索引,將其與長度進行比較,並分支到循環的開頭)。

在任何情況下,循環開銷都會為每次迭代添加增量,比較和分支。 而這種比較很可能需要一個指針去引用才能到達cards.length 循環開銷比你在循環中所做的工作要昂貴得多,這是非常合理的。

暫無
暫無

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

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