2016-04-18 8 views
4

Windows上でtoupperの実行速度が大幅に遅いクロスプラットフォーム(WindowsおよびLinux)アプリケーションで、エッジケースを診断しています。私はこれがtolowerでも同じであると仮定しています。ロケール設定時にWindows Cランタイムが遅くなる

私はこれをロケール情報が設定されていない単純なCプログラムでテストしました。ヘッダーファイルも含まれていましたが、パフォーマンスの違いはほとんどありませんでした。 Testはtoupper()関数に文字列を渡すために各文字を呼び出す百万回の繰り返しループでした。

ヘッダーファイルをインクルードした後、その行をインクルードした後は、はるかに遅くなり、多くのMS Cランタイムライブラリのロケール固有の関数が呼び出されます。これは問題ありませんが、パフォーマンスヒットは本当に悪いです。 Linuxでは、パフォーマンスに何の影響も与えません。

setlocale(LC_ALL, ""); // system default locale 

私は次のように設定している場合、それは、Linuxのように高速で動作しますが、すべてのロケール関数をスキップするように見えるん。

setlocale(LC_ALL, NULL); // should be interpreted as the same as below? 
OR 
setlocale(LC_ALL, "C"); 

注:セントOS

は、Windows、Linux上ノー速度差に遅いオランダ設定の設定と同じ結果を、試してみましたを実行しているLinux用のWindows 10 G ++用 のVisual Studio 2015。

私は間違ったことをやっているのですか、Windowsでのロケール設定にバグがありますか?それとも、Linuxが何をしていないのですか? 私はLinuxに慣れていないので、内部的に何をしているのか正確にはわからないので、私はLinuxアプリケーションでデバッグを行っていません。 これを整理するために次に試すべきは何ですか?テストのために、以下の

コード(Linuxの):Windows用の

// C++ is only used for timing. The original program is in C. 
#include <stdio.h> 
#include <stdlib.h> 
#include <ctype.h> 
#include <chrono> 
#include <locale.h> 

using namespace std::chrono; 

void strToUpper(char *strVal); 

int main() 
{ 

    typedef high_resolution_clock Clock; 
    high_resolution_clock::time_point t1 = Clock::now(); 

    // set locale 
    //setlocale(LC_ALL,"nl_NL"); 
    setlocale(LC_ALL,"en_US"); 

    // testing string 
    char str[] = "the quick brown fox jumps over the lazy dog"; 

    for (int i = 0; i < 1000000; i++) 
    { 
     strToUpper(str); 
    } 

    high_resolution_clock::time_point t2 = Clock::now(); 
    duration<double> time_span = duration_cast<duration<double>>(t2 - t1); 
    printf("chrono time %2.6f:\n",time_span.count()); 
} 

void strToUpper(char *strVal) 
{ 
    unsigned char *t; 
    t = (unsigned char *)strVal; 

    while (*t) 
    { 
     *t = toupper(*t); 
     *t++; 
    } 
} 

にローカル情報を変更します。

// set locale 
//setlocale(LC_ALL,"nld_nld"); 
setlocale(LC_ALL, "english_us"); 

あなたが完了した時刻における分離器からのロケールの変更、完全な停止を見ることができますコンマ対。

EDIT - プロファイリングデータ application function calls profiling 上記のように、_toupper_lからの子システムコールに費やされた時間はほとんどの時間です。 ロケール情報が設定されていないと、トゥーパーコールは子の_toupper_lを呼び出さず、非常に速くなります。

+1

C++、タグ付けする。 – 3442

+0

完全に最適化された「リリース」ビルドに対してこれをテストしていますか?また、GCCがループから完全に最適化している可能性もあります。なぜなら、あなたはループからの出力値を派生せず、文字列は 'volatile'ではないからです。 – paddy

+0

@paddyリリースビルドでは、私のオリジナルコードは最後に文字列を出力し、変数を設定しました。私はそれを単純化しました。反復回数を増やすと時間が長くなりますので、ループを最適化することはできません。これは、依然として出力を使用する生産アプリケーションのパフォーマンスの問題を説明していません。 –

答えて

0

LANG = C対LANG = Linuxで使用されるglibcの実装には他に何かが期待されます。

あなたのLinuxの結果は意味があります。あなたのテスト方法はおそらくOKです。プロファイラーを使用して、マイクロベンチマークがWindowsの機能の中で費やす時間を確認します。 Windowsの実装が問題になる場合は、C++ boost::to_upper_copy<std::string>のように、文字列全体を変換できるWindows関数があるかもしれません(ただし、それがもっと遅い場合を除きます)。


upcasingのASCII文字列はかなり効率的 SIMDベクトル化できることに注意してください。私はC SSE組み込み関数を使用して、単一のベクトルin another answerのcase-flip関数を書いた。それはフリップケースの代わりにアップケースに適合させることができる。長さが16バイトを超え、ASCIIであることを知っていると、多くの時間を費やすと、これは大きなスピードアップになるはずです。

実際には、ブーストのto_upper_copy() appears to compile to extremely slow code, like 10x slower than toupperです。私のベクトル化されたstrtoupper(dst,src)のリンクを参照してください。これはASCIIのみですが、ASCII以外のsrcバイトが検出された場合はフォールバックで拡張できます。


あなたの現在のコードはどのようにしてUTF-8を処理しますか?すべての文字が1バイトであると仮定すると、ASCII以外のロケールのサポートにはあまり効果がありません。 IIRCでは、WindowsはほとんどのものにUTF-16を使用していますが、2^16以上のコードポイントが必要であることが判明したため残念です。 UTF-16は、UTF-8のようなUnicodeの可変長エンコーディングですが、ASCIIを読むことの利点はありません。固定幅には多くの利点がありますが、残念ながらUTF-16であってもそのことは想定できません。 Javaもこのミスを犯し、UTF-16に悩まされています。


The glibc sourceです:

#define __ctype_toupper \ 
    ((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128) 
int toupper (int c) { 
    return c >= -128 && c < 256 ? __ctype_toupper[c] : c; 
} 

x86-64でのUbuntu 15.10の/lib/x86_64-linux-gnu/libc.so.6からASMは次のとおりです。

## disassembly from objconv -fyasm -v2 /lib/x86_64-linux-gnu/libc.so.6 /dev/stdout 2>&1 
toupper: 
    lea  edx, [rdi+80H]       ; 0002E300 _ 8D. 97, 00000080 
    movsxd rax, edi        ; 0002E306 _ 48: 63. C7 
    cmp  edx, 383        ; 0002E309 _ 81. FA, 0000017F 
    ja  ?_01766         ; 0002E30F _ 77, 19 
    mov  rdx, qword [rel ?_37923]    ; 0002E311 _ 48: 8B. 15, 00395AA8(rel) 
    sub  rax, -128        ; 0002E318 _ 48: 83. E8, 80 
    mov  rdx, qword [fs:rdx]      ; 0002E31C _ 64 48: 8B. 12 
    mov  rdx, qword [rdx]      ; 0002E320 _ 48: 8B. 12 
    mov  rdx, qword [rdx+48H]     ; 0002E323 _ 48: 8B. 52, 48 
    mov  eax, dword [rdx+rax*4]     ; 0002E327 _ 8B. 04 82 ## the final table lookup, indexing an array of 4B ints 
?_01766: 
    rep ret           ; actual objconv output shows the prefix on a separate line 

だから、引数のISN」の場合、早期アウトを取ります0〜0xFFの範囲にある(したがって、このブランチは完全には予測されません)、そうでなければ現在のロケールのテーブルを見つけます。これには3つのポインタ参照が含まれます:グローバルからのロード、 hread-local、もう一つの逆参照。次に、実際には256エントリ表に索引付けされます。

これはライブラリ関数全体です。逆アセンブリ内のtoupperラベルは、コードが呼び出すものです。 (動的リンクのためにPLTを経由する間接的なレイヤーを通して、最初の呼び出しが遅延シンボルの参照をトリガーした後、それはあなたのコードとライブラリ内の11個のinsnの間の特別なjmp命令の1つに過ぎません)。

+3

1.大文字の小文字のASCII文字は、必ずしもASCIIである必要はありません(具体的には、トルコ語では「i」です)2.ほとんどのヨーロッパ言語は1バイト表現で表現できます。 –

+0

@MartinBonner:そうですね、質問に対するあなたのコメントを見ました。ASCII->非ASCIIの16件未満の場合は、SSE4.2の 'PCMPISTRI'を使用して、ゼロバイトを終了して、ASCII SIMDとスカラーフォールバックを使用してロケール対応の 'strtoupper()'を実装してください(例:特殊な入力文字のベクトルをローカル配列からロードするなど)。 'トルコのロケールでは?glibcの' tolower'は、256の文字に対して何もしません。 –

+0

@MartinBonner:re:2:Unicodeはコードページを使用しません。ほとんどの言語で1バイト幅のエンコードが可能ですが、Unicodeには何もありませんそれと関係がありますか? –

関連する問題