6

以下は、現在の char* から 16 進文字列への関数です。ビット操作の演習として書きました。AMD Athlon MP 2800+ で 1,000 万バイトの配列を 16 進数に変換するには、約 7 ミリ秒かかります。私が見逃しているトリックや他の方法はありますか?

どうすればこれをより速くすることができますか?

g++ で -O3 でコンパイル

static const char _hex2asciiU_value[256][2] =
     { {'0','0'}, {'0','1'}, /* snip..., */ {'F','E'},{'F','F'} };

std::string char_to_hex( const unsigned char* _pArray, unsigned int _len )
{
    std::string str;
    str.resize(_len*2);
    char* pszHex = &str[0];
    const unsigned char* pEnd = _pArray + _len;

    clock_t stick, etick;
    stick = clock();
    for( const unsigned char* pChar = _pArray; pChar != pEnd; pChar++, pszHex += 2 ) {
        pszHex[0] = _hex2asciiU_value[*pChar][0];
        pszHex[1] = _hex2asciiU_value[*pChar][1];
    }
    etick = clock();

    std::cout << "ticks to hexify " << etick - stick << std::endl;

    return str;
}

アップデート

タイミングコードを追加

Brian R. Bondy : std::string をヒープに割り当てられたバッファに置き換え、 ofs*16 を ofs << 4 に変更します - ただし、ヒープに割り当てられたバッファは速度を落としているようですか? - 結果 ~11ms

Antti Sykäri : 内側のループを次のものに置き換えます

 int upper = *pChar >> 4;
 int lower = *pChar & 0x0f;
 pszHex[0] = pHex[upper];
 pszHex[1] = pHex[lower];

結果 ~8ms

Robert : 完全な 256 エントリのテーブルに置き換え_hex2asciiU_valueます。メモリ スペースは犠牲になりますが、結果は最大 7 ミリ秒になります。

HoyHoy : 誤った結果が生成されていたことに注意

4

16 に答える 16

9

このアセンブリ関数 (ここでの以前の投稿に基づいていますが、実際に機能させるには概念を少し変更する必要がありました) は、Core 2 Conroe 3Ghz の 1 つのコアで 1 秒あたり 33 億の入力文字 (66 億の出力文字) を処理します。Penryn の方がおそらく速いでしょう。

%include "x86inc.asm"

SECTION_RODATA
pb_f0: times 16 db 0xf0
pb_0f: times 16 db 0x0f
pb_hex: db 48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70

SECTION .text

; int convert_string_to_hex( char *input, char *output, int len )

cglobal _convert_string_to_hex,3,3
    movdqa xmm6, [pb_f0 GLOBAL]
    movdqa xmm7, [pb_0f GLOBAL]
.loop:
    movdqa xmm5, [pb_hex GLOBAL]
    movdqa xmm4, [pb_hex GLOBAL]
    movq   xmm0, [r0+r2-8]
    movq   xmm2, [r0+r2-16]
    movq   xmm1, xmm0
    movq   xmm3, xmm2
    pand   xmm0, xmm6 ;high bits
    pand   xmm2, xmm6
    psrlq  xmm0, 4
    psrlq  xmm2, 4
    pand   xmm1, xmm7 ;low bits
    pand   xmm3, xmm7
    punpcklbw xmm0, xmm1
    punpcklbw xmm2, xmm3
    pshufb xmm4, xmm0
    pshufb xmm5, xmm2
    movdqa [r1+r2*2-16], xmm4
    movdqa [r1+r2*2-32], xmm5
    sub r2, 16
    jg .loop
    REP_RET

x264 アセンブリ構文を使用しているため、移植性が高くなります (32 ビット対 64 ビットなど)。これを選択した構文に変換するのは簡単です。r0、r1、r2 はレジスタ内の関数への 3 つの引数です。疑似コードに少し似ています。または、x264 ツリーから common/x86/x86inc.asm を取得し、それを含めてネイティブに実行することもできます。

PS スタック オーバーフロー、そんな些細なことに時間を浪費するのは間違っていますか? それともこれがすごいの?

于 2008-09-17T01:20:38.447 に答える
4

より多くのメモリを犠牲にして、16 進コードの完全な 256 エントリ テーブルを作成できます。

static const char _hex2asciiU_value[256][2] =
    { {'0','0'}, {'0','1'}, /* ..., */ {'F','E'},{'F','F'} };

次に、インデックスをテーブルに直接挿入します。少しいじる必要はありません。

const char *pHexVal = pHex[*pChar];
pszHex[0] = pHexVal[0];
pszHex[1] = pHexVal[1];
于 2008-09-16T03:42:49.143 に答える
4

C 実装の高速化

これは、C++ 実装よりもほぼ 3 倍速く実行されます。よく似ているのでよくわかりません。私が最後に投稿した C++ の実装では、2 億文字の配列を実行するのに 6.8 秒かかりました。実装にかかった時間はわずか 2.2 秒でした。

#include <stdio.h>
#include <stdlib.h>

char* char_to_hex(const unsigned char* p_array, 
                  unsigned int p_array_len,
                  char** hex2ascii)
{
    unsigned char* str = malloc(p_array_len*2+1);
    const unsigned char* p_end = p_array + p_array_len;
    size_t pos=0;
    const unsigned char* p;
    for( p = p_array; p != p_end; p++, pos+=2 ) {
       str[pos] = hex2ascii[*p][0];
       str[pos+1] = hex2ascii[*p][1];
    }
    return (char*)str;
}

int main()
{
  size_t hex2ascii_len = 256;
  char** hex2ascii;
  int i;
  hex2ascii = malloc(hex2ascii_len*sizeof(char*));
  for(i=0; i<hex2ascii_len; i++) {
    hex2ascii[i] = malloc(3*sizeof(char));    
    snprintf(hex2ascii[i], 3,"%02X", i);
  }
  size_t len = 8;
  const unsigned char a[] = "DO NOT WANT";
  printf("%s\n", char_to_hex((const unsigned char*)a, len, (char**)hex2ascii));
}

ここに画像の説明を入力

于 2008-09-17T00:18:28.300 に答える
3

一度に32ビット(4文字)で操作し、必要に応じてテールを処理します。urlエンコードを使用してこの演習を行ったとき、各文字の完全なテーブルルックアップはロジック構造よりもわずかに高速でした。したがって、キャッシュの問題を考慮に入れるために、これをコンテキストでテストすることもできます。

于 2008-09-16T03:18:30.467 に答える
3

それは私のために働くunsigned char

unsigned char  c1 =  byteVal >> 4;
unsigned char  c2 =  byteVal & 0x0f;

c1 +=  c1 <= 9 ? '0' : ('a' - 10);
c2 +=  c2 <= 9 ? '0' : ('a' - 10);

std::string sHex("  ");
sHex[0] = c1 ;
sHex[1] = c2 ;


//sHex - contain what we need. For example "0f"
于 2012-01-12T16:33:10.517 に答える
2

16一つには、掛ける代わりにbitshift << 4

また、を使用せずstd::string、代わりにヒープ上にバッファを作成してからそれを作成しますdelete。文字列から必要なオブジェクトの破棄よりも効率的です。

于 2008-09-16T03:17:24.367 に答える
1

大きな違いはありません...*pChar-(ofs * 16)は[* pCHar&0x0F]で実行できます

于 2008-09-16T03:20:11.943 に答える
1

変化

    ofs = *pChar >> 4;
    pszHex[0] = pHex[ofs];
    pszHex[1] = pHex[*pChar-(ofs*16)];

    int upper = *pChar >> 4;
    int lower = *pChar & 0x0f;
    pszHex[0] = pHex[upper];
    pszHex[1] = pHex[lower];

約5%のスピードアップになります。

Robertが提案したように、一度に2バイトずつ結果を書き込むと、約18%高速化されます。コードは次のように変更されます。

_result.resize(_len*2);
short* pszHex = (short*) &_result[0];
const unsigned char* pEnd = _pArray + _len;

const char* pHex = _hex2asciiU_value;
for(const unsigned char* pChar = _pArray;
    pChar != pEnd;
    pChar++, ++pszHex )
{
    *pszHex = bytes_to_chars[*pChar];
}

必要な初期化:

short short_table[256];

for (int i = 0; i < 256; ++i)
{
    char* pc = (char*) &short_table[i];
    pc[0] = _hex2asciiU_value[i >> 4];
    pc[1] = _hex2asciiU_value[i & 0x0f];
}

Allan Windが指摘しているように、一度に2バイトまたは一度に4バイトを実行すると、おそらくさらに高速化されますが、奇妙な文字を処理する必要がある場合は注意が必要になります。

冒険心がある場合は、これを行うためにDuffのデバイスを適応させようとするかもしれません。

結果は、Intel CoreDuo2プロセッサとにありgcc -O3ます。

実際に結果が速くなることを常に測定してください。最適化を装った悲観化は無価値ではありません。

常に正しい結果が得られることをテストしてください。最適化を装ったバグは非常に危険です。

また、速度と読みやすさのトレードオフを常に念頭に置いてください。読み取り不能なコードを維持するには寿命が短すぎます。

あなたがどこに住んでいるかを知っている暴力的な精神病質者のためのコーディングへの義務的な言及。)

于 2008-09-16T04:04:23.927 に答える
1

これは私のバージョンであり、OP のバージョンとは異なり、std::basic_string連続した領域にデータがあるとは想定していません。

#include <string>

using std::string;

static char const* digits("0123456789ABCDEF");

string
tohex(string const& data)
{
    string result(data.size() * 2, 0);
    string::iterator ptr(result.begin());
    for (string::const_iterator cur(data.begin()), end(data.end()); cur != end; ++cur) {
        unsigned char c(*cur);
        *ptr++ = digits[c >> 4];
        *ptr++ = digits[c & 15];
    }
    return result;
}
于 2008-09-16T03:38:37.967 に答える
1

これはWindows + IA32だと思います。
2 つの 16 進数文字の代わりに short int を使用してみてください。

short int hex_table[256] = {'0'*256+'0', '1'*256+'0', '2'*256+'0', ..., 'E'*256+'F', 'F'*256+'F'};
unsigned short int* pszHex = &str[0];

stick = clock();

for (const unsigned char* pChar = _pArray; pChar != pEnd; pChar++) 
    *pszHex++ = hex_table[*pChar];

etick = clock();
于 2010-12-13T17:53:48.617 に答える
0

これを書いているときに示されている関数は、_hex2asciiU_valueが完全に指定されている場合でも、誤った出力を生成します。次のコードは機能し、私の2.33GHzMacbookProでは約1.9秒で2億文字で動作します。

#include <iostream>

using namespace std;

static const size_t _h2alen = 256;
static char _hex2asciiU_value[_h2alen][3];

string char_to_hex( const unsigned char* _pArray, unsigned int _len )
{
    string str;
    str.resize(_len*2);
    char* pszHex = &str[0];
    const unsigned char* pEnd = _pArray + _len;
    const char* pHex = _hex2asciiU_value[0];
    for( const unsigned char* pChar = _pArray; pChar != pEnd; pChar++, pszHex += 2 ) {
       pszHex[0] = _hex2asciiU_value[*pChar][0];
       pszHex[1] = _hex2asciiU_value[*pChar][1];
    }
    return str;
}


int main() {
  for(int i=0; i<_h2alen; i++) {
    snprintf(_hex2asciiU_value[i], 3,"%02X", i);
  }
  size_t len = 200000000;
  char* a = new char[len];
  string t1;
  string t2;
  clock_t start;
  srand(time(NULL));
  for(int i=0; i<len; i++) a[i] = rand()&0xFF;
  start = clock();
  t1=char_to_hex((const unsigned char*)a, len);
  cout << "char_to_hex conversion took ---> " << (clock() - start)/(double)CLOCKS_PER_SEC << " seconds\n";
}
于 2008-09-16T08:05:41.243 に答える
0

ここで速度にこだわる場合は、次のようにすることができます。

各文字は1バイトで、2つの16進値を表します。したがって、各文字は実際には2つの4ビット値です。

したがって、次のことができます。

  1. 乗算または同様の命令を使用して、4ビット値を8ビット値にアンパックします。
  2. SSSE3命令であるpshufbを使用します(ただし、Core2のみ)。16個の8ビット入力値の配列を受け取り、2番目のベクトルの16個の8ビットインデックスに基づいてそれらをシャッフルします。可能な文字は16文字しかないため、これは完全に適合します。入力配列は0からF文字のベクトルであり、インデックス配列は4ビット値のパックされていない配列です。

したがって、1つの命令で、通常1つだけ実行するよりも少ないクロックで16のテーブルルックアップを実行できます(pshufbはPenrynで1クロックのレイテンシーです)。

したがって、計算ステップでは:

  1. ABCDEFGHIJKLMNOP(入力値の64ビットベクトル、「ベクトルA」)-> 0A 0B 0C 0D 0E 0F 0G 0H 0I 0J 0K 0L 0M 0N 0O 0P(インデックスの128ビットベクトル、「ベクトルB」)。最も簡単な方法は、おそらく2つの64ビット乗算です。
  2. pshub [0123456789ABCDEF]、ベクトルB
于 2008-09-16T08:11:49.917 に答える
0

一度にもっと多くのバイトを実行する方が良いかどうかはわかりません...おそらく、大量のキャッシュミスが発生し、大幅に遅くなります。

ただし、ループを展開し、より大きなステップを実行し、ループを通過するたびにより多くの文字を実行して、ループのオーバーヘッドの一部を削除しようとする場合があります。

于 2008-09-16T08:25:35.313 に答える
0

コンパイラの最適化が最高の作業レベルに設定されていることを確認してください。

ご存じのように、gcc の「-O1」から「-03」のようなフラグです。

于 2008-09-16T03:41:54.683 に答える
0

Athlon 64 4200+ で常に最大 4 ミリ秒 (元のコードでは最大 7 ミリ秒)

for( const unsigned char* pChar = _pArray; pChar != pEnd; pChar++) {
    const char* pchars = _hex2asciiU_value[*pChar];
    *pszHex++ = *pchars++;
    *pszHex++ = *pchars;
}
于 2008-09-16T13:16:36.003 に答える
0

ポインターではなく配列へのインデックスを使用すると、動作が高速化されることがわかりました。それはすべて、コンパイラがどのように最適化するかによって異なります。重要なのは、プロセッサが [i*2+1] のような複雑なことを 1 つの命令で実行する命令を持っていることです。

于 2008-09-16T04:57:21.617 に答える