簡體   English   中英

是什么使它成為在Web瀏覽器中打印1到1,000,000(以空格分隔)的最快的JavaScript?

[英]What makes this the fastest JavaScript for printing 1 to 1,000,000 (separated by spaces) in a web browser?

我在這里閱讀有關JavaScript輸出緩沖的內容並且試圖讓我的頭腦繞過作者所說的在打印1到1,000,000網頁時最快的腳本。 (向下滾動到標題“獲勝的一百萬個數字腳本”。)稍微研究一下后,我有幾個問題:

  • 是什么讓這個腳本與其他方法相比如此高效?
  • 為什么緩沖加快了速度?
  • 如何確定要使用的適當緩沖區大小?
  • 這里有沒有人有任何可以進一步優化這個腳本的技巧?

(我意識到這可能是CS101,但我是那些受過抨擊的,自學成才的黑客之一,而且我希望能從這一方面的集體智慧中受益。謝謝!)

是什么讓這個腳本與其他方法相比如此高效?

作者正在對此算法進行多項優化。 這些中的每一個都需要相當深入地理解所使用的底層機制(例如Javascript,CPU,寄存器,緩存,視頻卡等)。 我認為他正在進行兩項關鍵優化(其余只是結冰):

  • 緩沖輸出
  • 使用整數數學而不是字符串操作

我會很快討論緩沖,因為你明確地詢問它。 他正在使用的整數數學有兩個性能優勢:整數加法每個操作比字符串操作更便宜,並且它使用更少的內存。

我不知道JavaScript和Web瀏覽器如何處理瀏覽器中整數到顯示字形的轉換,因此與字符串相比,將整數傳遞給document.write可能會有一些損失。 但是,他正在執行(1,000,000 / 1000)document.write調用,而不是1,000,000 - 1,000個整數添加。 這意味着他正在執行大約3個數量級的操作來形成消息,而不是將其發送到顯示器。 因此,將一個整數與一個字符串發送到document.write的代價必須超過3個數量級才能抵消操縱整數的性能優勢。

為什么緩沖加快了速度?

其工作原理的具體細節取決於您使用的平台,硬件和實現。 無論如何,它都是關於平衡你的算法與你的瓶頸誘導資源。

例如,在文件I / O的情況下,緩沖區是有用的,因為它利用了旋轉磁盤一次只能寫入一定量的事實。 給它太少的工作,當磁盤旋轉時,它不會使用在主軸頭部下方通過的每個可用位。 給它太多,你的應用程序必須等待(或者進入睡眠狀態)磁盤完成你的寫入時間,這可能花費在准備寫下一條記錄上! 確定文件I / O的理想緩沖區大小的一些關鍵因素包括:扇區大小,文件系統塊大小,交錯,磁頭數,旋轉速度和面密度等。

在CPU的情況下,它是關於保持管道滿的全部。 如果你給CPU太少的工作,它將花費時間旋轉NO OPs等待你的任務。 如果給CPU太多,則可能不會將請求分派給可以並行執行的其他資源,例如磁盤或視頻卡。 這意味着以后在CPU上必須等待這些返回而無需做任何事情。 CPU中緩沖的主要因素是使您需要的所有內容(對於CPU)盡可能靠近FPU / ALU。 在典型的體系結構中,這是(按照接近度降低的順序):寄存器,L1高速緩存,L2高速緩存,L3高速緩存,RAM。

在向屏幕寫入一百萬個數字的情況下,它是關於使用視頻卡在屏幕上繪制多邊形。 想想這樣。 假設對於添加的每個新號碼,視頻卡必須進行100,000,000次操作才能在屏幕上繪制多邊形。 在一個極端情況下,如果一次在頁面上放置一個數字,然后將您的視頻卡寫出來並為1,000,000個數字執行此操作,那么視頻卡將需要執行10 ^ 14次操作 - 100萬億次操作! 在另一個極端情況下,如果您將整個100萬個數字一次性地發送到視頻卡,那么只需要100,000,000次操作。 最佳點是在中間的某些位置。 如果你這樣做一次,CPU會做一個工作單元,並在GPU更新顯示器時等待很長時間。 如果你首先編寫整個1M項目字符串,那么當CPU流失時,GPU什么都不做。

如何確定要使用的緩沖區大小?

除非您針對具有特定算法的非常具體且定義良好的平台(例如,為互聯網路由編寫數據包路由),否則通常無法通過數學方式確定此問題。 通常,你會憑經驗找到它。 猜一個值,試一試,記錄結果然后選擇另一個。 您可以根據您管理的瓶頸,對從何處開始以及調查范圍進行一些有根據的猜測。

這里有沒有人有任何可以進一步優化這個腳本的技巧?

我不知道這是否會起作用,但我還沒有測試過,但是緩沖區大小通常是2的倍數,因為計算機的下限是二進制的,字大小通常是2的倍數(但這並不總是案件!)。 例如,64字節更可能是最優的60字節,1024更可能是最佳的1000.這個問題的一個瓶頸是迄今為止的大多數瀏覽器(谷歌Chrome是我的第一個例外)知道)讓javascript在與網頁渲染機制的其余部分相同的線程中連續運行。 這意味着javascript做了一些填充緩沖區的工作,然后等待很長時間,直到document.write調用返回。 如果javascript作為單獨的進程運行,異步,就像在chrome中一樣,你可能會獲得一個大的加速。 這當然是攻擊瓶頸的來源而不是使用它的算法,但有時候這是最好的選擇。

不像我想的那樣簡潔,但希望這是一個很好的起點。 緩沖是計算中各種性能問題的重要概念。 充分了解代碼使用的基礎機制(包括硬件和軟件)對於避免或解決性能問題非常有用。

我敢打賭,打印1m數字最慢的是瀏覽器重繪頁面,因此調用document.write()的次數越少越好。 當然,這需要與大型字符串連接進行平衡(因為它們涉及分配和復制)。

通過實驗確定正確的緩沖區大小。

在其他示例中,緩沖有助於沿自然邊界對齊。 這里有些例子

  • 32位CPU可以更有效地傳輸32位。
  • TCP / IP數據包具有最大大小。
  • 文件I / O類具有內部緩沖區。
  • 像TIFF這樣的圖像可以與它們的數據一起存儲在條帶中。

與其他系統的自然邊界對齊通常可以獲得性能優勢。

考慮它的一種方法是考慮對document.write()的單次調用非常昂貴。 但是,構建數組並將該數組連接到字符串中則不然。 因此,減少對document.write()的調用次數可以有效地減少寫入數字所需的總計算時間。

緩沖區是一種嘗試將兩種不同成本工作聯系在一起的方法。

計算和填充陣列對於小型陣列來說成本很低,大型陣列的成本很高。 無論寫入的大小如何,document.write都具有較大的常量成本,但對於較大的輸入,其擴展小於o(n)。

因此,排隊更大的字符串來編寫或緩沖它們可以提高整體性能。

順便說一下,這篇文章很好找。

所以這個讓我瘋狂,因為我不認為它真的是最快的。 所以這是我的實驗,任何人都可以玩。 也許我寫錯了什么,但看起來一次性而不是使用緩沖區實際上是一個更快的操作。 或者至少在我的實驗中。

<html>
<head>
<script type="text/javascript">
    function printAllNumberBuffered(n, bufferSize)
    {
        var startTime = new Date();

        var oRuntime = document.getElementById("divRuntime");
        var oNumbers = document.getElementById("divNumbers");

        var i = 0;
        var currentNumber;
        var pass = 0;
        var numArray = new Array(bufferSize);

        for(currentNumber = 1; currentNumber <= n; currentNumber++)
        {
            numArray[i] = currentNumber;

            if(currentNumber % bufferSize == 0 && currentNumber > 0)
            {
                oNumbers.textContent += numArray.join(' ');
                i = 0;
            }
            else
            {
                i++;
            }
        }

        if(i > 0)
        {
            numArray.splice(i - 1, bufferSize - 1);
            oNumbers.textContent += numArray.join(' ');
        }

        var endTime = new Date();

        oRuntime.innerHTML += "<div>Number: " + n + " Buffer Size: " + bufferSize + " Runtime: " + (endTime - startTime) + "</div>";
    }

    function PrintNumbers()
    {
        var oNumbers = document.getElementById("divNumbers");
        var tbNumber = document.getElementById("tbNumber");
        var tbBufferSize = document.getElementById("tbBufferSize");

        var n = parseInt(tbNumber.value);
        var bufferSize = parseInt(tbBufferSize.value);

        oNumbers.textContent = "";

        printAllNumberBuffered(n, bufferSize);
    }
</script>
</head>
<body>
<table  border="1">
    <tr>
        <td colspan="2">
            <div>Number:&nbsp;<input id="tbNumber" type="text" />Buffer Size:&nbsp;<input id="tbBufferSize" type="text" /><input type="button" value="Run" onclick="PrintNumbers();" /></div>
        </td>
    </tr>
    <tr>
        <td style="vertical-align:top" width="30%">
            <div  id="divRuntime"></div>
        </td>
        <td width="90%">
            <div id="divNumbers"></div>
        </td>
    </tr>
</table>
</body>
</html>

暫無
暫無

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

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