[英]Weird profiling results with zlib
我正在玩zlib
並擁有(簡化)代碼,如下所示:
#include <cstring> // memset
#include <string>
#include <zlib.h>
#include <cstdio>
const int compressionLevel_ = 9;
const size_t BUFFER_SIZE = 1024 * 8;
char buffer_[BUFFER_SIZE];
std::string compress(const char *data, size_t const size){
z_stream zs;
memset(&zs, 0, sizeof(zs));
if (deflateInit(&zs, compressionLevel_) != Z_OK)
return {};
zs.next_in = reinterpret_cast<Bytef *>( const_cast<char *>( data ) );
zs.avail_in = static_cast<uInt>( size );
std::string out;
int result;
do {
zs.next_out = reinterpret_cast<Bytef *>(buffer_);
zs.avail_out = BUFFER_SIZE;
result = deflate(&zs, Z_FINISH);
if (out.size() < zs.total_out){
// COMMENT / UNCOMMENT HERE !!!
out.append(buffer_, zs.total_out - out.size() );
}
} while (result == Z_OK);
deflateEnd(&zs);
if (result != Z_STREAM_END)
return {};
return out;
}
int main(){
const char *original = "Hello World";
size_t const original_size = strlen(original);
for(size_t i = 0; i < 1000000; ++i){
const auto cdata = compress(original, original_size);
}
}
實際代碼有點大,因為它解壓縮並檢查解壓縮字符串與原始字符串。
如果我用clang或gcc編譯,一切都會執行大約5秒。
但是......如果我評論這一行:
if (out.size() < zs.total_out){
// COMMENT / UNCOMMENT HERE !!!
// out.append(buffer_, zs.total_out - out.size() );
}
執行需要30秒!!!
如果我嘗試快速Linux服務器,時間分別為3秒vs 18秒。
我在MacOS上嘗試使用clang進行相同操作,並且時間上沒有太大差異。
我嘗試了不同級別的優化並且結果仍然存在 - 如果你發表評論,你會得到大約10倍的執行時間。
可能是什么原因?
更新
我用gcc嘗試了Cygwin - 那里的時間沒有區別。
我厭倦了使用gcc的Linux Arm CPU - 25秒vs 2:20分鍾。
如果你隨着time
運行它,你可以注意到一個有趣的事情,如果行取消注釋,你會得到這樣的結果:
$ time ./main
real 0m5.309s
user 0m5.304s
sys 0m0.004s
沒有什么特別的,只是在用戶空間花了大約5秒,正如預期的那樣。 但是對於線條評論你突然得到這個:
$ time ./main
real 0m29.061s
user 0m7.424s
sys 0m21.660s
所以並不是你的代碼突然變慢了六倍,實際上在內核上花了5000多倍,這是不尋常的,因為程序只是按其性質計算事物,沒有I / O或類似的東西。
oprofile證明了這一點,好的一個證明了這一點:
samples % image name symbol name
62451 45.2376 libz.so.1.2.8 /lib64/libz.so.1.2.8
61905 44.8421 libc-2.19.so memset
2752 1.9935 libc-2.19.so _int_free
2549 1.8464 libc-2.19.so _int_malloc
2474 1.7921 libc-2.19.so malloc_consolidate
2263 1.6392 no-vmlinux /no-vmlinux
1365 0.9888 libc-2.19.so malloc
723 0.5237 libc-2.19.so __memcpy_sse2_unaligned
711 0.5150 libstdc++.so.6.0.21 /usr/lib64/libstdc++.so.6.0.21
478 0.3462 libc-2.19.so free
366 0.2651 main main
6 0.0043 ld-2.19.so _dl_lookup_symbol_x
2 0.0014 ld-2.19.so _dl_relocate_object
2 0.0014 ld-2.19.so do_lookup_x
1 7.2e-04 ld-2.19.so _dl_name_match_p
1 7.2e-04 ld-2.19.so check_match.9478
1 7.2e-04 ld-2.19.so strcmp
1 7.2e-04 libc-2.19.so _dl_addr
雖然壞的是這樣的:
samples % image name symbol name
594605 74.6032 no-vmlinux /no-vmlinux
102981 12.9207 libc-2.19.so memset
72822 9.1368 libz.so.1.2.8 /lib64/libz.so.1.2.8
9093 1.1409 libc-2.19.so _int_malloc
3987 0.5002 libc-2.19.so sysmalloc
3365 0.4222 libc-2.19.so _int_free
2119 0.2659 libc-2.19.so brk
1958 0.2457 libc-2.19.so systrim.isra.1
1597 0.2004 libc-2.19.so free
1217 0.1527 libc-2.19.so malloc
1123 0.1409 libc-2.19.so sbrk
786 0.0986 libc-2.19.so __memcpy_sse2_unaligned
688 0.0863 libc-2.19.so __default_morecore
669 0.0839 main main
5 6.3e-04 ld-2.19.so _dl_relocate_object
4 5.0e-04 ld-2.19.so do_lookup_x
2 2.5e-04 ld-2.19.so strcmp
1 1.3e-04 ld-2.19.so _dl_lookup_symbol_x
1 1.3e-04 libc-2.19.so _dl_addr
如果你也看兩個二進制文件中的strace
,那么好的只會產生64個系統調用(至少在我的系統上),而壞的則會產生4000063個系統調用,其中大部分看起來像這樣:
brk(0x6c0000) = 0x6c0000
brk(0x6e2000) = 0x6e2000
brk(0x6d2000) = 0x6d2000
brk(0x6c2000) = 0x6c2000
brk(0x6c0000) = 0x6c0000
brk(0x6e2000) = 0x6e2000
brk(0x6d2000) = 0x6d2000
brk(0x6c2000) = 0x6c2000
brk(0x6c0000) = 0x6c0000
所以,我們在這里有內存分配 - 釋放循環。 我們在這里使用動態內存分配/釋放的唯一事情是輸出字符串,實際上,如果你將魔術字符串取消注釋,但是初始化字符串就像std::string out = "1";
,你得到與追加到字符串相同的“好”結果。
這必須是分配邏輯中的一些例子,其中你的usage-unusage模式觸發glibc首先分配一些內存(一個小塊,因此通過brk()
),然后將其釋放回系統。 使用初始化(靜態或附加)字符串,您將擁有不同的使用模式,並且glibc無法將內存返回給系統。 如果強制glibc沒有返回任何內存(參見man mallopt
),那么即使使用“bad”(注釋)二進制文件,結果也是一樣的:
$ time MALLOC_TRIM_THRESHOLD_=-1 ./main
real 0m5.094s
user 0m5.096s
sys 0m0.000s
很可能你不會在現實生活中遇到這個角落。 此外,如果您使用的是一些不同的C庫(甚至是不同版本的glibc),那么結果也可能不同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.