下面是我当前的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
替换内部循环 int upper = *pChar >> 4;
int lower = *pChar & 0x0f;
pszHex[0] = pHex[upper];
pszHex[1] = pHex[lower];
结果~8ms
Robert:用完整的256条表替换_hex2asciiU_value
,牺牲内存空间但结果大约7毫秒!
HoyHoy:注意到它产生的结果不正确
答案 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));
}
答案 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)
如果您对速度非常着迷,可以执行以下操作:
每个字符都是一个字节,表示两个十六进制值。因此,每个字符实际上是两个四位值。
因此,您可以执行以下操作:
因此,在单指令中,您将在比通常只做一次的时钟更少的时钟内执行 16个表查找(pshufb是Penryn上的1个时钟延迟)。
所以,在计算步骤中:
答案 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;
}