3 回答

TA貢獻(xiàn)1821條經(jīng)驗 獲得超6個贊
從您的 Perl 代碼(在您嘗試修復(fù)它之前):
@mymap = ();
$mymap["U.S."] = "Washington";
$mymap["U.K."] = "London";
這不是地圖而是數(shù)組。哈希映射的語法是:
%mymap;
$mymap{"U.S."} = ....
因此,您有效地做的是創(chuàng)建一個數(shù)組而不是哈希映射并始終訪問元素 0。請使用use strict;和use warnings;所有用Perl的時候,甚至有警告簡單的語法檢查會顯示你的問題:
perl -cw x.pl
Argument "U.S." isn't numeric in array element at x.pl line 9.
Argument "U.K." isn't numeric in array element at x.pl line 10.
除此之外,基準(zhǔn)測試的主要部分實際上沒有任何用處(分配一個變量并且從不使用它)并且一些編譯器可以檢測到它并簡單地將其優(yōu)化掉。
如果您檢查 Perl 程序生成的代碼,您會看到:
$ perl -MO=Deparse x.pl
@mymap = ();
$mymap[0] = 'Washington';
$mymap[0] = 'London';
...
for ($i = 0; $i < 1000000; ++$i) {
$x = $mymap[0];
}
也就是說,它在編譯時檢測到問題,并用對數(shù)組索引 0 的訪問來替換它。
因此,無論何時進(jìn)行基準(zhǔn)測試,您都需要:
檢查您的程序是否確實執(zhí)行了它應(yīng)該執(zhí)行的操作。
檢查編譯器是否不優(yōu)化,也不在編譯時執(zhí)行其他語言將在運行時執(zhí)行的內(nèi)容。任何沒有結(jié)果或可預(yù)測結(jié)果的語句都易于進(jìn)行這種優(yōu)化。
驗證您實際測量的是您打算測量的內(nèi)容,而不是其他內(nèi)容。即使對程序進(jìn)行很小的更改也會影響運行時間,因為需要分配內(nèi)存,而這些更改可能與您打算測量的內(nèi)容無關(guān)。在您的基準(zhǔn)測試中,您一次又一次地測量對同一哈希元素的訪問,而沒有對其他元素的訪問。這是一項可以很好地與處理器緩存配合使用的活動,但可能無法反映現(xiàn)實世界的使用情況。
而且,使用簡單的計時器并不是一個現(xiàn)實的基準(zhǔn)。系統(tǒng)上還有其他進(jìn)程,有調(diào)度程序,有緩存垃圾......而對于今天的 CPU,它高度依賴于系統(tǒng)上的負(fù)載,因為也許 CPU 會以比其他基準(zhǔn)測試更低的功耗模式運行一個基準(zhǔn)測試,即使用不同的 CPU 時鐘。例如,同一“基準(zhǔn)”的多次運行在我的系統(tǒng)上的測量時間在 100 毫秒到 150 毫秒之間變化。
基準(zhǔn)是謊言,而像您這樣的微觀基準(zhǔn)則是雙重的。

TA貢獻(xiàn)2041條經(jīng)驗 獲得超4個贊
我稍微修改了您的示例以獲取有關(guān)哈希表結(jié)構(gòu)的一些詳細(xì)信息:
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/time.h>
#include <chrono>
using namespace std;
int main()
{
std::unordered_map<std::string, std::string> mymap;
// populating container:
mymap["U.S."] = "Washington";
mymap["U.K."] = "London";
mymap["France"] = "Paris";
mymap["Russia"] = "Moscow";
mymap["China"] = "Beijing";
mymap["Germany"] = "Berlin";
mymap["Japan"] = "Tokyo";
mymap["China"] = "Beijing";
mymap["Italy"] = "Rome";
mymap["Spain"] = "Madrad";
std::hash<std::string> h;
for ( auto const& i : mymap )
{
printf( "hash(%s) = %ud\n", i.first.c_str(), h( i.first ) );
}
for ( int i = 0; i != mymap.bucket_count(); ++i )
{
auto const bucketsize = mymap.bucket_size( i );
if ( 0 != bucketsize )
{
printf( "size of bucket %d = %d\n", i, bucketsize );
}
}
auto const start = std::chrono::steady_clock::now();
string const x = "China";
std::string res;
for ( int i = 0; i < 1000000; i++ )
{
mymap.find( x );
}
auto const elapsed = std::chrono::steady_clock::now() - start;
printf( "%s\n", res );
printf( "took %d ms\n",
std::chrono::duration_cast<std::chrono::milliseconds>( elapsed ).count() );
return 0;
}
在我的系統(tǒng)上運行它,我得到了 ~68ms 的運行時間,輸出如下:
hash(Japan) = 3611029618d
hash(Spain) = 749986602d
hash(China) = 3154384700d
hash(U.S.) = 2546447179d
hash(Italy) = 2246786301d
hash(Germany) = 2319993784d
hash(U.K.) = 2699630607d
hash(France) = 3266727934d
hash(Russia) = 3992029278d
size of bucket 0 = 0
size of bucket 1 = 0
size of bucket 2 = 1
size of bucket 3 = 1
size of bucket 4 = 1
size of bucket 5 = 0
size of bucket 6 = 1
size of bucket 7 = 0
size of bucket 8 = 0
size of bucket 9 = 2
size of bucket 10 = 3
事實證明,哈希表沒有得到很好的優(yōu)化,并且包含一些沖突。進(jìn)一步打印bucket中的元素顯示西班牙和中國在bucket 9。bucket可能是一個鏈表,節(jié)點分布在內(nèi)存中,解釋了性能下降。
如果您選擇另一個哈希表大小,這樣就不會發(fā)生沖突,您可以獲得加速。我通過添加對其進(jìn)行了測試,mymap.rehash(1001)并在 44-52 毫秒之間將速度提高了 20-30%。
現(xiàn)在,另一點是計算“中國”的哈希值。該函數(shù)在每次迭代中執(zhí)行。當(dāng)我們切換到常量純 C 字符串時,我們可以消除這種情況:
#include <iostream>
#include <string>
#include <unordered_map>
#include <sys/time.h>
#include <chrono>
static auto constexpr us = "U.S.";
static auto constexpr uk = "U.K.";
static auto constexpr fr = "France";
static auto constexpr ru = "Russia";
static auto constexpr cn = "China";
static auto constexpr ge = "Germany";
static auto constexpr jp = "Japan";
static auto constexpr it = "Italy";
static auto constexpr sp = "Spain";
using namespace std;
int main()
{
std::unordered_map<const char*, std::string> mymap;
// populating container:
mymap[us] = "Washington";
mymap[uk] = "London";
mymap[fr] = "Paris";
mymap[ru] = "Moscow";
mymap[cn] = "Beijing";
mymap[ge] = "Berlin";
mymap[jp] = "Tokyo";
mymap[it] = "Rome";
mymap[sp] = "Madrad";
string const x = "China";
char const* res = nullptr;
auto const start = std::chrono::steady_clock::now();
for ( int i = 0; i < 1000000; i++ )
{
res = mymap[cn].c_str();
}
auto const elapsed = std::chrono::steady_clock::now() - start;
printf( "%s\n", res );
printf( "took %d ms\n",
std::chrono::duration_cast<std::chrono::milliseconds>( elapsed ).count() );
return 0;
}
在我的機器上,這將運行時間減少了 50% 到大約 20 毫秒。不同之處在于,它不是從字符串內(nèi)容計算散列值,而是將地址轉(zhuǎn)換為散列值,這要快得多,因為它只是將地址值作為 size_t 返回。我們也不需要重新散列,因為與cn.

TA貢獻(xiàn)1816條經(jīng)驗 獲得超6個贊
這只是表明對于這個特定用例,Go 哈希映射實現(xiàn)優(yōu)化得非常好。
mymap["China"]
調(diào)用專門針對字符串鍵優(yōu)化的mapaccess1_faststr。特別是對于小型單桶映射,甚至不為短(小于 32 字節(jié))字符串計算哈希碼。
- 3 回答
- 0 關(guān)注
- 339 瀏覽
添加回答
舉報