日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

使用 PHP 處理十億行數(shù)據(jù),如何極致提升處理速度?

來源: 責編: 時間:2024-04-23 09:14:40 245觀看
導讀譯者 | 劉汪洋審校 | 重樓如果在閱讀這篇文章之前,你還不了解“十億行挑戰(zhàn)”( The One Billion Row Challenge,1brc ),我推薦你訪問 Gunnar Morling 的 1brc GitHub 代碼倉庫了解更多詳情。我有兩位同事已經(jīng)參與這項挑戰(zhàn)并

譯者 | 劉汪洋uD828資訊網(wǎng)——每日最新資訊28at.com

審校 | 重樓uD828資訊網(wǎng)——每日最新資訊28at.com

如果在閱讀這篇文章之前,你還不了解“十億行挑戰(zhàn)”( The One Billion Row Challenge,1brc ),我推薦你訪問 Gunnar Morling 的 1brc GitHub 代碼倉庫了解更多詳情。uD828資訊網(wǎng)——每日最新資訊28at.com

我有兩位同事已經(jīng)參與這項挑戰(zhàn)并成功上榜,因此我也選擇加入。uD828資訊網(wǎng)——每日最新資訊28at.com

雖然 PHP 的執(zhí)行速度并不出名,但我正開發(fā)一個 PHP 分析器,因此我想親自測試一下 PHP的處理速度。uD828資訊網(wǎng)——每日最新資訊28at.com

第一種嘗試:簡單直接的方法

我首先克隆了挑戰(zhàn)的代碼倉庫,并生成了一個包含十億行數(shù)據(jù)的文件measurements.txt。接下來,我開始嘗試第一個解決方案:uD828資訊網(wǎng)——每日最新資訊28at.com

<?php$stations = [];$fp = fopen('measurements.txt', 'r');while ($data = fgetcsv($fp, null, ';')) {    if (!isset($stations[$data[0]])) {        $stations[$data[0]] = [            $data[1],            $data[1],            $data[1],            1        ];    } else {        $stations[$data[0]][3]++;        $stations[$data[0]][2] += $data[1];        if ($data[1] < $stations[$data[0]][0]) {            $stations[$data[0]][0] = $data[1];        }        if ($data[1] > $stations[$data[0]][1]) {            $stations[$data[0]][1] = $data[1];        }    }}ksort($stations);echo '{';foreach ($stations as $k => &$station) {    $station[2] = $station[2] / $station[3];    echo $k, '=', $station[0], '/', $station[2], '/', $station[1], ', ';}echo '}';

這段代碼邏輯簡單明了:打開文件并通過 fgetcsv()讀取數(shù)據(jù)。若之前未記錄過該站點,則創(chuàng)建一個新條目;否則,進行計數(shù)器增加、溫度累加,并檢查當前溫度是否刷新了最低或最高記錄,如是,則進行更新。uD828資訊網(wǎng)——每日最新資訊28at.com

處理完所有數(shù)據(jù)后,我使用ksort()對數(shù)組$stations進行排序,并輸出每個站點的最低溫度、平均溫度(總溫度/記錄數(shù))和最高溫度。uD828資訊網(wǎng)——每日最新資訊28at.com

令我驚訝的是,在我的筆記本電腦上運行這段簡單腳本竟然耗時達到了25分鐘。uD828資訊網(wǎng)——每日最新資訊28at.com

很明顯,我需要對這段代碼進行優(yōu)化,并對其進行性能分析:uD828資訊網(wǎng)——每日最新資訊28at.com

uD828資訊網(wǎng)——每日最新資訊28at.com

通過可視化的時間線,我們可以分析出腳本運行明顯受到 CPU 限制,腳本開始時的文件編譯時間可以忽略不計,且?guī)缀鯖]有垃圾收集事件發(fā)生。uD828資訊網(wǎng)——每日最新資訊28at.com

uD828資訊網(wǎng)——每日最新資訊28at.com

火焰圖清晰地顯示出,fgetcsv()函數(shù)占據(jù)了約 46% 的 CPU 時間。uD828資訊網(wǎng)——每日最新資訊28at.com

使用 fgets() 替代 fgetcsv()

為了提升性能,我決定用fgets()替換fgetcsv()函數(shù)來逐行讀取數(shù)據(jù),并手動按;字符進行分割。uD828資訊網(wǎng)——每日最新資訊28at.com

// ...while ($data = fgets($fp, 999)) {    $pos = strpos($data, ';');    $city = substr($data, 0, $pos);    $temp = substr($data, $pos + 1, -1);// ...

同時,我還把代碼中的$data[0]重命名為$city,$data[1]重命名為$temp,以增強代碼的可讀性。uD828資訊網(wǎng)——每日最新資訊28at.com

這個簡單的修改使得腳本運行時間大幅減少到 19 分鐘 49 秒,雖然時間仍然較長,但相比之前已經(jīng)減少了 21%。uD828資訊網(wǎng)——每日最新資訊28at.com

uD828資訊網(wǎng)——每日最新資訊28at.com

通過火焰圖的比較,可以看到在替換后 CPU 的時間利用率發(fā)生了變化,詳細的根幀分析也揭示了具體的性能瓶頸位置:uD828資訊網(wǎng)——每日最新資訊28at.com

uD828資訊網(wǎng)——每日最新資訊28at.com

在腳本的第 18 行和第 23 行花費了大約 38% 的CPU時間。uD828資訊網(wǎng)——每日最新資訊28at.com

18 | $stations[$city][3]++;   | // ...23 | if ($temp > $stations[$city][1]) {

第 18 行是數(shù)組$stations的首次訪問和增量操作,而第 23 行進行了一次看似不那么耗時的比較操作。盡管如此,進一步優(yōu)化有助于揭示這些操作中潛在的性能開銷。uD828資訊網(wǎng)——每日最新資訊28at.com

盡可能使用引用

為了提高性能,我決定在處理數(shù)組時使用引用,以避免每次訪問數(shù)組時都對$stations數(shù)組中的鍵進行搜索。這相當于為數(shù)組中的"當前"站點設置了一個緩存。uD828資訊網(wǎng)——每日最新資訊28at.com

代碼如下:uD828資訊網(wǎng)——每日最新資訊28at.com

$station = &$stations[$city];$station[3]++;$station[2] += $temp;// 替代原有的$stations[$city][3]++;$stations[$city][2] += $temp;

這一改變實際上大大減少了執(zhí)行時間,將其縮短到 17 分鐘 48 秒,進一步減少了 **10% **的運行時間。uD828資訊網(wǎng)——每日最新資訊28at.com

條件判斷優(yōu)化

在審查代碼的過程中,我注意到了以下片段:uD828資訊網(wǎng)——每日最新資訊28at.com

if ($temp < $station[0]) {    $station[0] = $temp;} elseif ($temp > $station[1]) {    $station[1] = $temp;}

考慮到一個溫度值如果低于最小值,則不可能同時高于最大值,因此我使用elseif來優(yōu)化條件判斷,這可能會節(jié)省一些 CPU 周期。uD828資訊網(wǎng)——每日最新資訊28at.com

需要指出的是,由于我不知道m(xù)easurements.txt中溫度值的排列順序,根據(jù)這個順序,首先檢查最小值還是最大值可能會有所不同。uD828資訊網(wǎng)——每日最新資訊28at.com

這次優(yōu)化將時間進一步縮短到 17 分鐘 30 秒,節(jié)省了大約 2% 的時間,雖然這個提升并不是非常顯著。uD828資訊網(wǎng)——每日最新資訊28at.com

執(zhí)行類型轉(zhuǎn)換

PHP是一種動態(tài)類型語言,我在編程初期非常欣賞它這一特點,因為它簡化了許多問題。然而,另一方面,明確變量類型能幫助解釋引擎更高效地執(zhí)行代碼。uD828資訊網(wǎng)——每日最新資訊28at.com

$temp = (float)substr($data, $pos + 1, -1);

令人驚訝的是,這個簡單的類型轉(zhuǎn)換把腳本執(zhí)行時間縮短至 13 分鐘 32 秒,性能提升達到了驚人的 **21% **!uD828資訊網(wǎng)——每日最新資訊28at.com

uD828資訊網(wǎng)——每日最新資訊28at.com

18 | $station = &$stations[$city];   | // ...23 | } elseif ($temp > $station[1]) {

在優(yōu)化后,第 18 行顯示數(shù)組訪問的 CPU 時間消耗從 11% 減少,這是因為減少了在 PHP 的哈希映射(關聯(lián)數(shù)組的底層數(shù)據(jù)結(jié)構(gòu))中搜索鍵的次數(shù)。uD828資訊網(wǎng)——每日最新資訊28at.com

第 23 行的 CPU 時間從約 32% 減少到約 15%。這是因為避免了類型轉(zhuǎn)換的開銷。在優(yōu)化之前,$temp、$station[0]和$station[1]是字符串類型,因此 PHP 在每次比較時必須將它們轉(zhuǎn)換為浮點數(shù)。uD828資訊網(wǎng)——每日最新資訊28at.com

引入 JIT

在優(yōu)化過程中,我還嘗試啟用了 PHP 的 JIT(即時編譯器),它是 OPCache 的一部分。默認情況下,OPCache 在 CLI(命令行界面)模式下被禁用,因此需通過將opcache.enable_cli 設置為 on來啟用。此外,雖然JIT默認為開啟狀態(tài),但由于緩沖區(qū)大小默認設置為0,實際上處于禁用狀態(tài)。通過將opcache.jit-buffer-size設置為10M,我有效地啟用了 JIT。uD828資訊網(wǎng)——每日最新資訊28at.com

啟用 JIT 后,腳本執(zhí)行時間驚人地縮減至 7 分鐘 19 秒,速度提升了 45.9%。uD828資訊網(wǎng)——每日最新資訊28at.com

進一步優(yōu)化

通過這系列優(yōu)化,我將腳本的執(zhí)行時間從最初的 25 分鐘大幅降低到了約 7 分鐘。在這個過程中,我注意到使用fgets()讀取一個 13GB 的文件時,竟然分配了大約 56GiB 每分鐘的 RAM,這顯然是不合理的。經(jīng)過調(diào)查,我發(fā)現(xiàn)省略fgets()的長度參數(shù)可以大量減少內(nèi)存分配:uD828資訊網(wǎng)——每日最新資訊28at.com

while ($data = fgets($fp)) {// 替代之前的while ($data = fgets($fp, 999)) {

這個簡單變化雖然只使性能提高了約 1%,但將內(nèi)存分配從每分鐘 56GiB 降至每分鐘 6GiB,顯著減少了內(nèi)存占用。這一改進雖然對執(zhí)行時間影響不大,但減少內(nèi)存消耗對于大規(guī)模數(shù)據(jù)處理仍然是一個重要的優(yōu)化方向。uD828資訊網(wǎng)——每日最新資訊28at.com

以上優(yōu)化展示了在 PHP 性能調(diào)優(yōu)中考慮各種因素的重要性,包括代碼邏輯優(yōu)化、類型明確、JIT編譯以及內(nèi)存管理等,共同作用下可以顯著提升應用性能。uD828資訊網(wǎng)——每日最新資訊28at.com

還能更快嗎?

到目前為止,我使用的單線程方法,與許多PHP程序默認的單線程方式相符,但通過使用parallel 擴展,PHP 實際上能在用戶空間內(nèi)實現(xiàn)多線程操作。uD828資訊網(wǎng)——每日最新資訊28at.com

性能分析明確指出,在 PHP 中進行數(shù)據(jù)讀取成為了性能瓶頸。雖然從 fgetcsv() 切換到 fgets() 并手動進行字符串分割有所改進,但這種方式仍舊相對耗時。因此,我們考慮采用多線程的方式來并行地讀取和處理數(shù)據(jù),并在之后將各個工作線程的中間結(jié)果合并起來。uD828資訊網(wǎng)——每日最新資訊28at.com

<?php$file = 'measurements.txt';$threads_cnt = 16;/** * 計算并返回每個線程應處理的文件塊的起始和結(jié)束位置。 * 這些位置將基于 /n 字符進行對齊,因為我們使用 `fgets()` 進行讀取, * 它會讀取直到遇到 /n 字符為止。 * * @return array<int, array{0: int, 1: int}> */function get_file_chunks(string $file, int $cpu_count): array {    $size = filesize($file);    if ($cpu_count == 1) {        $chunk_size = $size;    } else {        $chunk_size = (int) ($size / $cpu_count);    }    $fp = fopen($file, 'rb');    $chunks = [];    $chunk_start = 0;    while ($chunk_start < $size) {        $chunk_end = min($size, $chunk_start + $chunk_size);        if ($chunk_end < $size) {            fseek($fp, $chunk_end);            fgets($fp); // 將文件指針移動到下一個 /n 字符            $chunk_end = ftell($fp);        }        $chunks[] = [            $chunk_start,            $chunk_end        ];        $chunk_start = $chunk_end;    }    fclose($fp);    return $chunks;}/** * 該函數(shù)負責打開指定的 `$file` 文件,并從 `$chunk_start` 開始讀取處理數(shù)據(jù), * 直到達到 `$chunk_end`。 * * 返回的結(jié)果數(shù)組以城市名作為鍵,其值為一個數(shù)組,包含最低溫度(鍵 0)、最高溫度(鍵 1)、 * 溫度總和(鍵 2)及溫度計數(shù)(鍵 3)。 * * @return array<string, array{0: float, 1: float, 2: float, 3: int}> */$process_chunk = function (string $file, int $chunk_start, int $chunk_end): array {    $stations = [];    $fp = fopen($file, 'rb');    fseek($fp, $chunk_start);    while ($data = fgets($fp)) {        $chunk_start += strlen($data);        if ($chunk_start > $chunk_end) {            break;        }        $pos2 = strpos($data, ';');        $city = substr($data, 0, $pos2);        $temp = (float)substr($data, $pos2 + 1, -1);        if (isset($stations[$city])) {            $station = &$stations[$city];            $station[3]++;            $station[2] += $temp;            if ($temp < $station[0]) {                $station[0] = $temp;            } elseif ($temp > $station[1]) {                $station[1] = $temp;            }        } else {            $stations[$city] = [                $temp,                $temp,                $temp,                1            ];        }    }    return $stations;};$chunks = get_file_chunks($file, $threads_cnt);$futures = [];for ($i = 0; $i < $threads_cnt; $i++) {    $runtime = new /parallel/Runtime();    $futures[$i] = $runtime->run(        $process_chunk,        [            $file,            $chunks[$i][0],            $chunks[$i][1]        ]    );}$results = [];for ($i = 0; $i < $threads_cnt; $i++) {    // 等待線程結(jié)果,主線程在此處阻塞直至獲取結(jié)果    $chunk_result = $futures[$i]->value();    foreach ($chunk_result as $city => $measurement) {        if (isset($results[$city])) {            $result = &$results[$city];            $result[2] += $measurement[2];            $result[3] += $measurement[3];            if ($measurement[0] < $result[0]) {                $result[0] = $measurement[0];            }            if ($measurement[1] > $result[1]) {                $result[1] = $measurement[1];            }        } else {            $results[$city] = $measurement;        }    }}ksort($results);echo '{', PHP_EOL;foreach ($results as $k => &$station) {    echo "/t", $k, '=', $station[0], '/', ($station[2] / $station[3]), '/', $station[1], ',', PHP_EOL;}echo '}', PHP_EOL;

該段代碼主要執(zhí)行以下操作:首先,它掃描文件并將其分割成以 /n 為界的塊(利用 fgets() 進行讀取)。準備好這些塊后,我啟動了 $threads_cnt 個工作線程,它們分別打開相同的文件并跳轉(zhuǎn)到分配給它們的塊的起始位置,繼續(xù)讀取并處理數(shù)據(jù)直到塊結(jié)束,返回中間結(jié)果。最后,在主線程中合并、排序并輸出這些結(jié)果。uD828資訊網(wǎng)——每日最新資訊28at.com

利用多線程處理,這個過程只需:

本文鏈接:http://m.www897cc.com/showinfo-26-84720-0.html使用 PHP 處理十億行數(shù)據(jù),如何極致提升處理速度?

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 如何編寫可讀性高的 C/C++代碼?

下一篇: 探秘Python神器:eli5模塊如何解讀機器學習模型的預測結(jié)果?

標簽:
  • 熱門焦點
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
在线亚洲伦理| 亚洲黄色性网站| 国产精品日韩精品欧美精品| 国产精品igao视频网网址不卡日韩| 欧美午夜免费电影| 国产欧美在线看| 影音先锋亚洲视频| 99riav国产精品| 性欧美暴力猛交69hd| 狂野欧美一区| 欧美少妇一区| 国语自产在线不卡| 日韩午夜在线播放| 欧美一级大片在线观看| 免费久久99精品国产| 欧美视频免费在线| 国产综合香蕉五月婷在线| 最新亚洲电影| 午夜激情综合网| 免费观看国产成人| 国产精品毛片在线看| 在线看片成人| 亚洲愉拍自拍另类高清精品| 玖玖在线精品| 国产精品视频免费在线观看| 1204国产成人精品视频| 亚洲一区二区三区在线视频| 久久一区国产| 国产精品区一区| 亚洲国产婷婷香蕉久久久久久99| 亚洲欧美日本精品| 欧美成人精品一区二区| 国产日韩精品在线观看| 99精品视频免费| 久久视频在线视频| 国产精品区一区二区三区| 亚洲国产精品久久久| 午夜精品视频一区| 欧美日韩精品伦理作品在线免费观看| 国产亚洲va综合人人澡精品| 日韩视频―中文字幕| 久久永久免费| 国产精品一国产精品k频道56| 亚洲理论在线观看| 久久久91精品| 国产精品亚洲激情| 在线视频欧美一区| 欧美激情bt| 激情视频亚洲| 欧美一区二区观看视频| 欧美日韩中文字幕在线| 亚洲黄一区二区三区| 久久精品亚洲一区二区三区浴池| 国产精品久久久久久久7电影 | 亚洲欧洲在线看| 久久久91精品国产| 国产一区二区高清| 性色av一区二区怡红| 欧美色123| 日韩特黄影片| 欧美激情精品久久久久久免费印度 | 亚洲少妇最新在线视频| 欧美福利视频一区| 在线观看av一区| 久久久91精品国产一区二区三区 | 亚洲国产天堂久久综合网| 久久精品亚洲精品国产欧美kt∨| 国产精品一区二区久久久 | 欧美成人免费网| 在线日本成人| 久久夜色精品| 在线观看成人网| 久久香蕉国产线看观看av| 韩国在线视频一区| 久久久久久久久久久成人| 国产日韩欧美综合| 欧美专区日韩专区| 国产三区二区一区久久| 性欧美精品高清| 国产日韩欧美精品在线| 午夜精品成人在线| 国产女主播一区二区三区| 欧美亚洲一区在线| 国产午夜精品理论片a级大结局 | 亚洲男人的天堂在线| 国产精品五月天| 午夜精品一区二区三区在线| 国产精品一区二区在线观看不卡| 亚洲欧美日韩在线综合| 国产欧美另类| 久久精品官网| 亚洲第一主播视频| 欧美精品v日韩精品v国产精品| 亚洲三级视频| 欧美三级韩国三级日本三斤| 一区二区三区产品免费精品久久75 | 欧美另类专区| 中日韩高清电影网| 国产精品任我爽爆在线播放| 亚洲欧美激情精品一区二区| 国产精品资源在线观看| 欧美在线一级视频| 在线观看日韩av电影| 欧美激情一区| 亚洲一卡二卡三卡四卡五卡| 国产日韩欧美在线| 老司机精品导航| 亚洲精品一区二区在线观看| 欧美三级电影大全| 欧美一级黄色录像| 在线观看的日韩av| 欧美三区在线| 久久福利一区| 亚洲欧洲一二三| 国产精品视频自拍| 另类天堂视频在线观看| 一区二区三区|亚洲午夜| 国产精品一区一区三区| 狂野欧美激情性xxxx欧美| 日韩一区二区精品视频| 国产欧美一区二区三区视频| 麻豆国产精品777777在线| 夜夜嗨av色综合久久久综合网| 国产精品免费一区豆花| 久久琪琪电影院| 一区二区三区鲁丝不卡| 国产一区欧美| 欧美伦理影院| 欧美在线视频a| 亚洲日本中文字幕区| 国产免费成人在线视频| 欧美福利视频在线观看| 午夜久久久久久久久久一区二区| 1024国产精品| 国产精品人人做人人爽| 欧美不卡在线视频| 亚洲欧美综合国产精品一区| 亚洲国产精品一区二区第四页av | 亚洲三级网站| 国产亚洲精品一区二区| 欧美日本中文| 久久久久国产精品厨房| 一区二区三区av| 尤物在线精品| 国产精品免费一区豆花| 免费在线欧美黄色| 性xx色xx综合久久久xx| 亚洲精品一线二线三线无人区| 国产欧美日本一区二区三区| 欧美国产日韩一区二区| 欧美一区二区三区四区在线观看| 妖精成人www高清在线观看| 一区二区三区自拍| 国产精品久久久久久久久借妻| 免费久久精品视频| 午夜免费久久久久| 一本久久a久久精品亚洲| 精品电影在线观看| 国产精品久久一级| 欧美日韩成人在线| 久久人人97超碰精品888| 国产精品99久久久久久久女警 | 亚洲在线视频| 亚洲精品视频中文字幕| 韩日视频一区| 国产精品一香蕉国产线看观看| 欧美日韩不卡视频| 狼狼综合久久久久综合网| 欧美在线一二三四区| 亚洲欧美日韩国产另类专区| av不卡免费看| 亚洲日韩成人| 亚洲黄色影院| 亚洲电影网站| 精品99视频| 国产亚洲精品一区二555| 国产精品久久久久久久电影| 欧美日韩一区二区高清| 欧美国产一区二区在线观看| 久久久女女女女999久久| 欧美一级理论片| 午夜精品在线| 亚洲男人av电影| 亚洲图片欧美午夜| 在线亚洲国产精品网站| 日韩视频在线免费观看| 亚洲日本成人网| 亚洲精品国精品久久99热| 亚洲电影免费| 在线观看中文字幕不卡| 一区二区在线观看视频| 国产自产女人91一区在线观看| 国产伦理一区| 国产女精品视频网站免费| 国产欧美日韩不卡| 国产视频久久| 国产一区二区三区观看| 国产一区二区三区久久久| 国产午夜精品在线| 国产一区 二区 三区一级| 国产日韩欧美亚洲一区| 国产综合欧美| 激情偷拍久久| 亚洲国产欧美一区|