char []到十六进制字符串练习

时间:2008-09-16 03:15:40

标签: c++ optimization hex

下面是我当前的char *到十六进制字符串函数。我把它写成一个位操作练习。在AMD Athlon MP 2800+上花费大约7毫秒来对1000万字节阵列进行取消。是否有任何技巧或其他方式我缺席?

如何让它更快?

用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,并将* 16更改为ofs&lt;&lt; 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:注意到它产生的结果不正确

16 个答案:

答案 0 :(得分:9)

这个汇编函数(基于我之前的帖子,但我不得不修改一下这个概念以使其实际工作)在Core 2的一个核心上处理每秒33亿个输入字符(66亿个输出字符) Conroe 3Ghz。 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是寄存器中函数的三个参数。它有点像伪代码。或者你可以从x264树中获取common / x86 / x86inc.asm并将其包含在本地运行。

P.S。 Stack Overflow,我是不是浪费时间在这么微不足道的事情上?或者这太棒了?

答案 1 :(得分:4)

以更多内存为代价,您可以创建十六进制代码的完整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];

答案 2 :(得分:4)

更快的C Implmentation

这比C ++实现快了近3倍。不确定为什么它非常相似。对于我发布的最后一个C ++实现,它花了6.8秒来运行200,000,000个字符数组。实施只用了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));
}

enter image description here

答案 3 :(得分:3)

一次操作32位(4个字符),然后根据需要处理尾部。当我使用url编码进行此练习时,每个char的完整表查找比逻辑结构略快,因此您可能希望在上下文中对此进行测试以考虑缓存问题。

答案 4 :(得分: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"

答案 5 :(得分:2)

首先,不要乘以16而是bitshift << 4

也不要使用std::string,而只是在堆上创建一个缓冲区,然后delete。它将比字符串中所需的对象破坏更有效。

答案 6 :(得分:1)

不会产生很大的不同...... * pChar-(ofs * 16)可以用[* pCHar&amp;为0x0F]

答案 7 :(得分: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;
}

答案 8 :(得分: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的建议,将结果写入两个字节,结果加速率约为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];
}

一次做2个字节或一次4个字节可能会导致更高的加速,正如Allan Wind所指出的那样,但是当你必须处理奇数字符时它会变得更加棘手。 / p>

如果您有冒险精神,可以尝试调整Duff's device来执行此操作。

结果在Intel Core Duo 2处理器和gcc -O3上。

始终衡量您实际上获得了更快的结果 - 假装优化的悲观情绪并非毫无价值。

始终测试您获得了正确的结果 - 假装优化的错误非常危险。

并且始终牢记速度与可读性之间的权衡 - 任何人维持不可读代码的生命都太短暂。

Obligatory reference编码violent psychopath who knows where you live。)

答案 9 :(得分:1)

我认为这是Windows + IA32 尝试使用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();

答案 10 :(得分:0)

确保您的编译器优化已打开到最高工作级别。

你知道,gcc中的'-O1'到'-03'等标志。

答案 11 :(得分:0)

我发现在数组中使用索引而不是指针可以加快速度。这完全取决于编译器选择优化的方式。关键是处理器有指令在一条指令中执行复杂的操作,如[i * 2 + 1]。

答案 12 :(得分:0)

当我写这个时显示的函数产生不正确的输出,即使完全指定_hex2asciiU_value也是如此。以下代码可以工作,在我的2.33GHz Macbook Pro上运行大约1.9秒,200,000,000,000个字符。

#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";
}

答案 13 :(得分:0)

如果您对速度非常着迷,可以执行以下操作:

每个字符都是一个字节,表示两个十六进制值。因此,每个字符实际上是两个四位值。

因此,您可以执行以下操作:

  1. 使用乘法或类似指令将四位值解包为8位值。
  2. 使用pshufb,SSSE3指令(仅限Core2)。它需要16个8位输入值的数组,并根据第二个向量中的16个8位索引对它们进行混洗。由于你只有16个可能的角色,这非常适合;输入数组是0到F字符的向量,索引数组是4位值的解包数组。
  3. 因此,在单指令中,您将在比通常只做一次的时钟更少的时钟内执行 16个表查找(pshufb是Penryn上的1个时钟延迟)。

    所以,在计算步骤中:

    1. A B C D E F G H I J K L M N O P(输入值的64位矢量,“矢量A”) - &gt; 0A 0B 0C 0D 0E 0F 0G 0H 0I 0J 0K 0L 0M 0N 0O 0P(索引的128位向量,“向量B”)。最简单的方法可能是两个64位乘法。
    2. pshub [0123456789ABCDEF],Vector B

答案 14 :(得分:0)

我不确定一次做多个字节会更好......你可能只会获得大量的缓存未命中并显着降低它的速度。

你可能尝试的是展开循环,每次循环时采取更大的步骤并做更多的字符,以消除一些循环开销。

答案 15 :(得分: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;
}