[英]In C, Fastest way to find and count specific values in binary file of Integers?
[英]Looking for the absolute FASTEST way to write integers as individual digits - chars to a file in C - including microoptimizations
我正在 C 中開發一個程序,主要目標是絕對速度——這是一個代碼性能競賽。 有更多的方法可以加速程序,但是,最大的加速潛力是在 I/O 操作中,特別是保存到文本文件。 該文件的結構如下:每行 3 個任意數字的整數,以空格分隔。 整數是事先已知的,只需將它們轉換為字符串並寫入 output 緩沖區即可。
整數范圍僅從-1
到INT_MAX
。
緩沖區大小根據正在寫入的數據而變化(我設置它),但大多數時候,寫入的文件大小在 100 兆字節到超過 1 GB 之間,緩沖區在 4 到 8 MB 之間。 主要的寫循環是這樣的:
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
const size_t w_bufsize = get_bufsize(param);
void *buf = NULL;
posix_memalign(&buf, sysconf(_SC_PAGE_SIZE), w_bufsize)
posix_fadvise(fd, 0, 0, POSIX_FADV_NOREUSE);
size_t num_written = 0;
size_t f_idx = 0;
for (int i = 0; i < num_ints; ++i) {
myStruct *str = main_struct->structs + i;
f_idx = fast_write_3_ints(buf, str->int1, str->int2, str->int3, f_idx);
if (f_idx + BYTES_PER_ROW > w_bufsize) {
write(fd, buf, f_idx) != f_idx
if (num_written)
posix_fadvise(fd, (num_written - 1) * w_bufsize, w_bufsize,
POSIX_FADV_DONTNEED);
f_idx = 0;
++num_written;
}
(返回值檢查和釋放/關閉為了便於閱讀而縮寫)
為了將整數轉換為文本,我使用此方法: https://kenny-peng.com/2021/05/28/printing_integers_fast.html我通過繞過臨時緩沖區並將字符直接存儲到 output 來進一步改進它緩沖區(在我的機器上性能增加 10-15%)。 這是我的代碼的縮寫(如果可能)版本
size_t fast_write_3_ints(char *out_buf, int num1, int num2, int num3,
size_t idx)
{
char *temp_ptr = NULL;
int n_digits = 0;
if (num1 < 0) {
out_buf[idx++] = '-';
num1 = -num1;
}
if (num1 < 10) {
out_buf[idx++] = num1 + '0';
} else {
idx += count_digits(num1);
temp_ptr = out_buf + idx;
for (; num1 >= 1000; num1 /= 1000) {
temp_ptr -= 3;
lookup_digits(temp_ptr, num1 % 1000, 3);
}
if (num1) {
num1 %= 1000;
n_digits = count_digits(num1);
lookup_digits(temp_ptr - n_digits, num1, n_digits);
}
}
out_buf[idx++] = ' ';
// write int 2 and 3 - abbreviated
out_buf[idx++] = '\n';
return idx;
}
static void lookup_digits(char *arr, int num, char write_size)
{
static const char table[3000] __attribute__((aligned(64))) =
"000001002003004005006007008009"
"010011012013014015016017018019"
"020021022023024025026027028029"
"030031032033034035036037038039"
"040041042043044045046047048049"
"050051052053054055056057058059"
"060061062063064065066067068069"
"070071072073074075076077078079"
"080081082083084085086087088089"
"090091092093094095096097098099"
"100101102103104105106107108109"
"110111112113114115116117118119"
"120121122123124125126127128129"
"130131132133134135136137138139"
"140141142143144145146147148149"
"150151152153154155156157158159"
"160161162163164165166167168169"
"170171172173174175176177178179"
"180181182183184185186187188189"
"190191192193194195196197198199"
"200201202203204205206207208209"
"210211212213214215216217218219"
"220221222223224225226227228229"
"230231232233234235236237238239"
"240241242243244245246247248249"
"250251252253254255256257258259"
"260261262263264265266267268269"
"270271272273274275276277278279"
"280281282283284285286287288289"
"290291292293294295296297298299"
"300301302303304305306307308309"
"310311312313314315316317318319"
"320321322323324325326327328329"
"330331332333334335336337338339"
"340341342343344345346347348349"
"350351352353354355356357358359"
"360361362363364365366367368369"
"370371372373374375376377378379"
"380381382383384385386387388389"
"390391392393394395396397398399"
"400401402403404405406407408409"
"410411412413414415416417418419"
"420421422423424425426427428429"
"430431432433434435436437438439"
"440441442443444445446447448449"
"450451452453454455456457458459"
"460461462463464465466467468469"
"470471472473474475476477478479"
"480481482483484485486487488489"
"490491492493494495496497498499"
"500501502503504505506507508509"
"510511512513514515516517518519"
"520521522523524525526527528529"
"530531532533534535536537538539"
"540541542543544545546547548549"
"550551552553554555556557558559"
"560561562563564565566567568569"
"570571572573574575576577578579"
"580581582583584585586587588589"
"590591592593594595596597598599"
"600601602603604605606607608609"
"610611612613614615616617618619"
"620621622623624625626627628629"
"630631632633634635636637638639"
"640641642643644645646647648649"
"650651652653654655656657658659"
"660661662663664665666667668669"
"670671672673674675676677678679"
"680681682683684685686687688689"
"690691692693694695696697698699"
"700701702703704705706707708709"
"710711712713714715716717718719"
"720721722723724725726727728729"
"730731732733734735736737738739"
"740741742743744745746747748749"
"750751752753754755756757758759"
"760761762763764765766767768769"
"770771772773774775776777778779"
"780781782783784785786787788789"
"790791792793794795796797798799"
"800801802803804805806807808809"
"810811812813814815816817818819"
"820821822823824825826827828829"
"830831832833834835836837838839"
"840841842843844845846847848849"
"850851852853854855856857858859"
"860861862863864865866867868869"
"870871872873874875876877878879"
"880881882883884885886887888889"
"890891892893894895896897898899"
"900901902903904905906907908909"
"910911912913914915916917918919"
"920921922923924925926927928929"
"930931932933934935936937938939"
"940941942943944945946947948949"
"950951952953954955956957958959"
"960961962963964965966967968969"
"970971972973974975976977978979"
"980981982983984985986987988989"
"990991992993994995996997998999";
memcpy(arr, table + 3 * num + 3 - write_size, write_size);
}
static int count_digits(int num)
{
if (num < 100000)
if (num < 1000)
if (num < 100)
if (num < 10)
return 1;
else
return 2;
else
return 3;
else if (num < 10000)
return 4;
else
return 5;
else if (num < 10000000)
if (num < 1000000)
return 6;
else
return 7;
else if (num < 100000000)
return 8;
else if (num < 1000000000)
return 9;
else
return 10;
}
這是目前的主要生產代碼。 下面我將描述我嘗試過的替代方案以及結果如何。
我還必須注意,我的電腦是一台 14" Macbook Pro,配備 M1 Pro 芯片和非常快的 SSD,這使得 IO 操作與主要計算相比完全可以忽略不計。但是,評估服務器/機器的規格非常不同(可能), 保存文件是迄今為止最慢的一點。我還注意到一些更改使它在我的機器上表現更好但在實際評估器上表現更差(可能取決於緩存大小/內存速度)。
我還嘗試按照此處所述實現無查找的 int-to-string 處理: https://johnnylee-sde.github.io/Fast-unsigned-integer-to-string/這並沒有提高性能超過 run-to - 在我的機器上運行差異。
我還嘗試將表格擴展到 4*10000 個數字,但它在我的機器上僅提高了 3-5% 的性能並且實際上在評估系統中使它變得更糟(可能 CPU/內存慢很多)。
還有什么我可以優化的嗎? 我的想法不多了。 歷史上最快的代碼版本保存到文件的速度比我的實現快 18%。
一個線程解決了一些確切的問題,但具有不同的功能(在我看來)速度較慢並且執行更多的操作? 將圖形保存到文件的最快方法 C還是我應該嘗試將單個大緩沖區例程集成到我的算法中,而不是寫入st_blksize
大小的緩沖區? 非常感謝任何幫助或建議
編輯: Function 確定 output 緩沖區大小(將參數視為要寫入的行數)
size_t get_bufsize(int param)
{
size_t bufsize = 4096;
if (param >= 1000 && param < 10000)
bufsize <<= 4;
else if (param >= 10000 && param < 100000)
bufsize <<= 6;
else if (param >= 100000 && param < 1000000)
bufsize <<= 8;
else if (param >= 1000000 && param <= 5000000)
bufsize <<= 10;
else if (param > 5000000)
bufsize <<= 11;
// printf("Buffer size: %zu\n", bufsize);
return bufsize;
}
編輯 2 :整數范圍僅從 -1 到 INT_MAX。
以下是嘗試提高代碼效率的一些指導:
如果在遺留系統上運行,您應該指定O_BINARY
以確保write
系統調用不會執行某些系統特定的轉換。
將緩沖區刷新到磁盤時,您應該嘗試只寫入整數頁並將剩余的塊移動到緩沖區的開頭。 分配適當數量的 4K 頁面加上一些 slack 並寫入 4K 頁面是分配大量頁面和發出部分寫入的更好方法。
你的 function fast_write_3_ints
有多余的語句num1 %= 1000;
以及if (num1)
測試。 它可以進一步簡化以提高小值的速度:
size_t fast_write_3_ints(char *out_buf, int num1, int num2, int num3,
size_t idx)
{
char *temp_ptr;
int n_digits;
if (num1 < 0) {
out_buf[idx++] = '-';
num1 = -num1;
}
if (num1 < 1000) {
if (num1 < 10) {
out_buf[idx++] = num1 + '0';
} else {
n_digits = 2 + (num1 >= 100);
lookup_digits(out_buf + idx, num1, n_digits));
idx += n_digits;
}
} else {
n_digits = count_digits(num1);
idx += n_digits;
temp_ptr = out_buf + idx;
while (n_digits > 3) {
int digits = num1 % 1000;
num1 /= 1000; // group division and modulo
temp_ptr -= 3;
lookup_digits(temp_ptr, digits, 3);
n_digits -= 3;
}
lookup_digits(temp_ptr - n_digits, num1, n_digits);
}
out_buf[idx++] = ' ';
// write int 2 and 3 - abbreviated
out_buf[idx++] = '\n';
return idx;
}
count_digits
使用無分支代碼可能會讓你獲得一些速度提升:static int count_digits(int num) {
return 1 + (num > 9) + (num > 99) + (num > 999) +
(num > 9999) + (num > 99999) + (num > 999999) +
(num > 9999999) + (num > 99999999) + (num > 999999999);
}
如果您正在嘗試優化以避免不必要地執行代碼並且唯一的負值是 -1,請更改:
if (num1 < 0) {
out_buf[idx++] = '-';
num1 = -num1;
}
if (num1 < 10) {
out_buf[idx++] = num1 + '0';
} else {
到
if (num1 < 10) {
if (num1 < 0) num = 1, out_buf[idx++] = '-';
out_buf[idx++] = num1 + '0';
} else {
此外,您似乎在某些特殊情況下嘗試處理剩余的 1,2 或 3 位數字。 這是不必要的。
下面的示例代碼從@chqrlie“借用”了無分支的 function。 它還計算雙/三位數而不是索引到 LUT。 想想那個 LUT……將前 100 個值分割成第二個“兩位數”function,修剪前導零,並停止對指針和計數執行神秘計算。 (我不是建議你使用這些函數。太多的算術運算發生了。你可以使用兩個不同的轉換函數......也可以不使用。)最后,這個例子只處理正數並且只轉換一個。
void lookup_2_digits( char *p, int n ) { // Use a LUT... I didn't for this example
p[1] = (char)(n % 10 + '0'); n /= 10;
p[0] = (char)(n + '0');
}
void lookup_3_digits( char *p, int n ) { // Use a LUT... I didn't for this example
p[2] = (char)(n % 10 + '0'); n /= 10;
p[1] = (char)(n % 10 + '0'); n /= 10;
p[0] = (char)(n + '0');
}
int count_digits(int n) {
return 1+ (n > 9) + (n > 99) + (n > 999)
+ (n > 9999) + (n > 99999) + (n > 999999)
+ (n > 9999999) + (n > 99999999) + (n > 999999999);
}
void doit( int num1 ) {
char out_buf[512] = {0};
int idx = 0;
idx += count_digits( num1 );
char *temp_ptr = out_buf + idx;
do {
if( num1 <= 99 ) {
if( num1 <= 9 )
/* Can deal with -1 here */
*--temp_ptr = num1 + '0';
else
lookup_2_digits( temp_ptr-2, num1 );
num1 = 0;
} else {
lookup_3_digits( temp_ptr -= 3, num1 % 1000 );
num1 /= 1000;
}
} while( num1 > 0 );
puts( out_buf );
}
int main( void ) {
doit( 2165536 );
return 0;
}
int
與int_fast32_t
考慮int_fast32_t
而不是int
,因為 64 位類型可能更快。
避免使用 2 個值進行區間測試
使用簡化的if
樹可能會有一點改進。
此外,傾向於首先測試較大的值,因為它更有可能匹配。
uint_fast32_t get_bufsize(int param) {
#define BLOCK ((uint_fast32_t) 4096)
if (param >= 5000000) {
return BLOCK << 11;
}
if (param >= 1000000) {
return BLOCK << 10;
}
if (param >= 100000) {
return BLOCK << 8;
}
if (param >= 10000) {
return BLOCK) << 6;
}
if (param >= 1000) {
return BLOCK << 4;
}
return BLOCK;
}
unsigned
與int
我從來沒有遇到過使用int
比unsigned
更快,但是使用unsigned
有可能獲得更快的代碼。 可以嘗試的東西。 在if (num1 < 0)
測試之后,代碼可以轉移到無符號數學並且可能會看到邊際改進。
我懷疑其中任何一個都會顯着改進,但可能會推動更快的代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.