将 0x1234 转换为 0x11223344
Posted
技术标签:
【中文标题】将 0x1234 转换为 0x11223344【英文标题】:Convert 0x1234 to 0x11223344 【发布时间】:2014-02-14 04:20:40 【问题描述】:如何以高性能的方式将十六进制数 0x1234 扩展为 0x11223344?
unsigned int c = 0x1234, b;
b = (c & 0xff) << 4 | c & 0xf | (c & 0xff0) << 8
| (c & 0xff00) << 12 | (c & 0xf000) << 16;
printf("%p -> %p\n", c, b);
输出:
0x1234 -> 0x11223344
我需要这个来进行颜色转换。用户以 0xARGB 的形式提供他们的数据,我需要将其转换为 0xAARRGGBB
。是的,可能有数百万,因为每个都可能是一个像素。 1000x1000 像素等于一百万。
实际情况更复杂,因为单个 32 位值同时包含前景色和背景色。所以0xARGBargb
变成:[ 0xAARRGGBB, 0xaarrggbb ]
哦,是的,还有一件事,在实际应用中我也否定 alpha,因为在 OpenGL 中,0xFF 是不透明的,而 0x00 是最透明的,这在大多数情况下很不方便,因为通常你只需要一个 @987654327 @ 部分和透明度被假定为不存在。
【问题讨论】:
我不同意这属于代码审查,除非 SO 的范围发生了根本性的变化。过去,有关执行此类操作的最有效方法的问题一直是热门话题。 @exebook '如果我删除我的代码会怎样' 不,我认为这不是一个好主意!最好尝试指出您对代码的具体疑虑! 您不应该使用%p
打印 unsigned int
值 - 使用 %#x
从 unsigned int
获取带有前导 0x
的十六进制输出。
值得一提的是,除非您需要在某种实时软件上每秒运行数百万次,否则实现的性能成本几乎为零。当我们的代码在可以实时处理视频效果的处理器上运行时,以可读性为代价保存一两个 & 是荒谬的。
另一种方法是使用查找表(如果您可以节省 256kB)...
【参考方案1】:
这可以使用SSE2 来完成,如下所示:
void ExpandSSE2(unsigned __int64 in, unsigned __int64 &outLo, unsigned __int64 &outHi)
__m128i const mask = _mm_set1_epi16((short)0xF00F);
__m128i const mul0 = _mm_set1_epi16(0x0011);
__m128i const mul1 = _mm_set1_epi16(0x1000);
__m128i v;
v = _mm_cvtsi64_si128(in); // Move the 64-bit value to a 128-bit register
v = _mm_unpacklo_epi8(v, v); // 0x12 -> 0x1212
v = _mm_and_si128(v, mask); // 0x1212 -> 0x1002
v = _mm_mullo_epi16(v, mul0); // 0x1002 -> 0x1022
v = _mm_mulhi_epu16(v, mul1); // 0x1022 -> 0x0102
v = _mm_mullo_epi16(v, mul0); // 0x0102 -> 0x1122
outLo = _mm_extract_epi64(v, 0);
outHi = _mm_extract_epi64(v, 1);
当然,您希望将函数的核心内容放在一个内部循环中并取出常量。您还需要跳过 x64 寄存器并将值直接加载到 128 位 SSE 寄存器中。有关如何执行此操作的示例,请参阅下面性能测试中的 SSE2 实现。
其核心有五个指令,一次对四个颜色值执行操作。因此,每个颜色值只有大约 1.25 条指令。还应该注意的是,SSE2 在 x64 可用的任何地方都可用。
在此处对各种解决方案进行性能测试 一些人提到,知道什么更快的唯一方法是运行代码,这是无可争辩的事实。所以我已经将一些解决方案编译成性能测试,这样我们就可以将苹果与苹果进行比较。我选择了我认为与其他解决方案显着不同以至于需要测试的解决方案。所有解决方案都从内存中读取,对数据进行操作,然后写回内存。在实践中,当输入数据中没有完整的 16 个字节要处理时,一些 SSE 解决方案将需要额外注意对齐和处理情况。我测试的代码是使用在 4+ GHz Core i7 上运行的 Visual Studio 2013 在发行版下编译的 x64。
这是我的结果:
ExpandOrig: 56.234 seconds // From asker's original question
ExpandSmallLUT: 30.209 seconds // From Dmitry's answer
ExpandLookupSmallOneLUT: 33.689 seconds // from Dmitry's answer
ExpandLookupLarge: 51.312 seconds // A straightforward lookup table
ExpandAShelly: 43.829 seconds // From AShelly's answer
ExpandAShellyMulOp: 43.580 seconds // AShelly's answer with an optimization
ExpandSSE4: 17.854 seconds // My original SSE4 answer
ExpandSSE4Unroll: 17.405 seconds // My original SSE4 answer with loop unrolling
ExpandSSE2: 17.281 seconds // My current SSE2 answer
ExpandSSE2Unroll: 17.152 seconds // My current SSE2 answer with loop unrolling
在上面的测试结果中,您会看到我包含了提问者的代码、三个查找表实现,包括 Dmitry 的回答中提出的小型查找表实现。 AShelly 的解决方案也包括在内,还有一个我做了优化的版本(可以消除一个操作)。我包括了我最初的 SSE4 实现,以及我后来制作的一个高级 SSE2 版本(现在反映为答案),以及两者的展开版本,因为它们是这里最快的,我想看看展开它们的速度有多少.我还包括了 AShelly 答案的 SSE4 实现。
到目前为止,我必须宣布自己是赢家。但源代码如下,因此任何人都可以在他们的平台上对其进行测试,并将他们自己的解决方案包含在测试中,看看他们是否制定了更快的解决方案。
#define DATA_SIZE_IN ((unsigned)(1024 * 1024 * 128))
#define DATA_SIZE_OUT ((unsigned)(2 * DATA_SIZE_IN))
#define RERUN_COUNT 500
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <utility>
#include <emmintrin.h> // SSE2
#include <tmmintrin.h> // SSSE3
#include <smmintrin.h> // SSE4
void ExpandOrig(unsigned char const *in, unsigned char const *past, unsigned char *out)
unsigned u, v;
do
// Read in data
u = *(unsigned const*)in;
v = u >> 16;
u &= 0x0000FFFF;
// Do computation
u = (u & 0x00FF) << 4
| (u & 0x000F)
| (u & 0x0FF0) << 8
| (u & 0xFF00) << 12
| (u & 0xF000) << 16;
v = (v & 0x00FF) << 4
| (v & 0x000F)
| (v & 0x0FF0) << 8
| (v & 0xFF00) << 12
| (v & 0xF000) << 16;
// Store data
*(unsigned*)(out) = u;
*(unsigned*)(out + 4) = v;
in += 4;
out += 8;
while (in != past);
unsigned LutLo[256],
LutHi[256];
void MakeLutLo(void)
for (unsigned i = 0, x; i < 256; ++i)
x = i;
x = ((x & 0xF0) << 4) | (x & 0x0F);
x |= (x << 4);
LutLo[i] = x;
void MakeLutHi(void)
for (unsigned i = 0, x; i < 256; ++i)
x = i;
x = ((x & 0xF0) << 20) | ((x & 0x0F) << 16);
x |= (x << 4);
LutHi[i] = x;
void ExpandLookupSmall(unsigned char const *in, unsigned char const *past, unsigned char *out)
unsigned u, v;
do
// Read in data
u = *(unsigned const*)in;
v = u >> 16;
u &= 0x0000FFFF;
// Do computation
u = LutHi[u >> 8] | LutLo[u & 0xFF];
v = LutHi[v >> 8] | LutLo[v & 0xFF];
// Store data
*(unsigned*)(out) = u;
*(unsigned*)(out + 4) = v;
in += 4;
out += 8;
while (in != past);
void ExpandLookupSmallOneLUT(unsigned char const *in, unsigned char const *past, unsigned char *out)
unsigned u, v;
do
// Read in data
u = *(unsigned const*)in;
v = u >> 16;
u &= 0x0000FFFF;
// Do computation
u = ((LutLo[u >> 8] << 16) | LutLo[u & 0xFF]);
v = ((LutLo[v >> 8] << 16) | LutLo[v & 0xFF]);
// Store data
*(unsigned*)(out) = u;
*(unsigned*)(out + 4) = v;
in += 4;
out += 8;
while (in != past);
unsigned LutLarge[256 * 256];
void MakeLutLarge(void)
for (unsigned i = 0; i < (256 * 256); ++i)
LutLarge[i] = LutHi[i >> 8] | LutLo[i & 0xFF];
void ExpandLookupLarge(unsigned char const *in, unsigned char const *past, unsigned char *out)
unsigned u, v;
do
// Read in data
u = *(unsigned const*)in;
v = u >> 16;
u &= 0x0000FFFF;
// Do computation
u = LutLarge[u];
v = LutLarge[v];
// Store data
*(unsigned*)(out) = u;
*(unsigned*)(out + 4) = v;
in += 4;
out += 8;
while (in != past);
void ExpandAShelly(unsigned char const *in, unsigned char const *past, unsigned char *out)
unsigned u, v, w, x;
do
// Read in data
u = *(unsigned const*)in;
v = u >> 16;
u &= 0x0000FFFF;
// Do computation
w = (((u & 0xF0F) * 0x101) & 0xF000F) + (((u & 0xF0F0) * 0x1010) & 0xF000F00);
x = (((v & 0xF0F) * 0x101) & 0xF000F) + (((v & 0xF0F0) * 0x1010) & 0xF000F00);
w += w * 0x10;
x += x * 0x10;
// Store data
*(unsigned*)(out) = w;
*(unsigned*)(out + 4) = x;
in += 4;
out += 8;
while (in != past);
void ExpandAShellyMulOp(unsigned char const *in, unsigned char const *past, unsigned char *out)
unsigned u, v;
do
// Read in data
u = *(unsigned const*)in;
v = u >> 16;
u &= 0x0000FFFF;
// Do computation
u = ((((u & 0xF0F) * 0x101) & 0xF000F) + (((u & 0xF0F0) * 0x1010) & 0xF000F00)) * 0x11;
v = ((((v & 0xF0F) * 0x101) & 0xF000F) + (((v & 0xF0F0) * 0x1010) & 0xF000F00)) * 0x11;
// Store data
*(unsigned*)(out) = u;
*(unsigned*)(out + 4) = v;
in += 4;
out += 8;
while (in != past);
void ExpandSSE4(unsigned char const *in, unsigned char const *past, unsigned char *out)
__m128i const mask0 = _mm_set1_epi16((short)0x8000),
mask1 = _mm_set1_epi8(0x0F),
mul = _mm_set1_epi16(0x0011);
__m128i u, v, w, x;
do
// Read input into low 8 bytes of u and v
u = _mm_load_si128((__m128i const*)in);
v = _mm_unpackhi_epi8(u, u); // Expand each single byte to two bytes
u = _mm_unpacklo_epi8(u, u); // Do it again for v
w = _mm_srli_epi16(u, 4); // Copy the value into w and shift it right half a byte
x = _mm_srli_epi16(v, 4); // Do it again for v
u = _mm_blendv_epi8(u, w, mask0); // Select odd bytes from w, and even bytes from v, giving the the desired value in the upper nibble of each byte
v = _mm_blendv_epi8(v, x, mask0); // Do it again for v
u = _mm_and_si128(u, mask1); // Clear the all the upper nibbles
v = _mm_and_si128(v, mask1); // Do it again for v
u = _mm_mullo_epi16(u, mul); // Multiply each 16-bit value by 0x0011 to duplicate the lower nibble in the upper nibble of each byte
v = _mm_mullo_epi16(v, mul); // Do it again for v
// Write output
_mm_store_si128((__m128i*)(out ), u);
_mm_store_si128((__m128i*)(out + 16), v);
in += 16;
out += 32;
while (in != past);
void ExpandSSE4Unroll(unsigned char const *in, unsigned char const *past, unsigned char *out)
__m128i const mask0 = _mm_set1_epi16((short)0x8000),
mask1 = _mm_set1_epi8(0x0F),
mul = _mm_set1_epi16(0x0011);
__m128i u0, v0, w0, x0,
u1, v1, w1, x1,
u2, v2, w2, x2,
u3, v3, w3, x3;
do
// Read input into low 8 bytes of u and v
u0 = _mm_load_si128((__m128i const*)(in ));
u1 = _mm_load_si128((__m128i const*)(in + 16));
u2 = _mm_load_si128((__m128i const*)(in + 32));
u3 = _mm_load_si128((__m128i const*)(in + 48));
v0 = _mm_unpackhi_epi8(u0, u0); // Expand each single byte to two bytes
u0 = _mm_unpacklo_epi8(u0, u0); // Do it again for v
v1 = _mm_unpackhi_epi8(u1, u1); // Do it again
u1 = _mm_unpacklo_epi8(u1, u1); // Again for u1
v2 = _mm_unpackhi_epi8(u2, u2); // Again for v1
u2 = _mm_unpacklo_epi8(u2, u2); // Again for u2
v3 = _mm_unpackhi_epi8(u3, u3); // Again for v2
u3 = _mm_unpacklo_epi8(u3, u3); // Again for u3
w0 = _mm_srli_epi16(u0, 4); // Copy the value into w and shift it right half a byte
x0 = _mm_srli_epi16(v0, 4); // Do it again for v
w1 = _mm_srli_epi16(u1, 4); // Again for u1
x1 = _mm_srli_epi16(v1, 4); // Again for v1
w2 = _mm_srli_epi16(u2, 4); // Again for u2
x2 = _mm_srli_epi16(v2, 4); // Again for v2
w3 = _mm_srli_epi16(u3, 4); // Again for u3
x3 = _mm_srli_epi16(v3, 4); // Again for v3
u0 = _mm_blendv_epi8(u0, w0, mask0); // Select even bytes from w, and odd bytes from v, giving the the desired value in the upper nibble of each byte
v0 = _mm_blendv_epi8(v0, x0, mask0); // Do it again for v
u1 = _mm_blendv_epi8(u1, w1, mask0); // Again for u1
v1 = _mm_blendv_epi8(v1, x1, mask0); // Again for v1
u2 = _mm_blendv_epi8(u2, w2, mask0); // Again for u2
v2 = _mm_blendv_epi8(v2, x2, mask0); // Again for v2
u3 = _mm_blendv_epi8(u3, w3, mask0); // Again for u3
v3 = _mm_blendv_epi8(v3, x3, mask0); // Again for v3
u0 = _mm_and_si128(u0, mask1); // Clear the all the upper nibbles
v0 = _mm_and_si128(v0, mask1); // Do it again for v
u1 = _mm_and_si128(u1, mask1); // Again for u1
v1 = _mm_and_si128(v1, mask1); // Again for v1
u2 = _mm_and_si128(u2, mask1); // Again for u2
v2 = _mm_and_si128(v2, mask1); // Again for v2
u3 = _mm_and_si128(u3, mask1); // Again for u3
v3 = _mm_and_si128(v3, mask1); // Again for v3
u0 = _mm_mullo_epi16(u0, mul); // Multiply each 16-bit value by 0x0011 to duplicate the lower nibble in the upper nibble of each byte
v0 = _mm_mullo_epi16(v0, mul); // Do it again for v
u1 = _mm_mullo_epi16(u1, mul); // Again for u1
v1 = _mm_mullo_epi16(v1, mul); // Again for v1
u2 = _mm_mullo_epi16(u2, mul); // Again for u2
v2 = _mm_mullo_epi16(v2, mul); // Again for v2
u3 = _mm_mullo_epi16(u3, mul); // Again for u3
v3 = _mm_mullo_epi16(v3, mul); // Again for v3
// Write output
_mm_store_si128((__m128i*)(out ), u0);
_mm_store_si128((__m128i*)(out + 16), v0);
_mm_store_si128((__m128i*)(out + 32), u1);
_mm_store_si128((__m128i*)(out + 48), v1);
_mm_store_si128((__m128i*)(out + 64), u2);
_mm_store_si128((__m128i*)(out + 80), v2);
_mm_store_si128((__m128i*)(out + 96), u3);
_mm_store_si128((__m128i*)(out + 112), v3);
in += 64;
out += 128;
while (in != past);
void ExpandSSE2(unsigned char const *in, unsigned char const *past, unsigned char *out)
__m128i const mask = _mm_set1_epi16((short)0xF00F),
mul0 = _mm_set1_epi16(0x0011),
mul1 = _mm_set1_epi16(0x1000);
__m128i u, v;
do
// Read input into low 8 bytes of u and v
u = _mm_load_si128((__m128i const*)in);
v = _mm_unpackhi_epi8(u, u); // Expand each single byte to two bytes
u = _mm_unpacklo_epi8(u, u); // Do it again for v
u = _mm_and_si128(u, mask);
v = _mm_and_si128(v, mask);
u = _mm_mullo_epi16(u, mul0);
v = _mm_mullo_epi16(v, mul0);
u = _mm_mulhi_epu16(u, mul1); // This can also be done with a right shift of 4 bits, but this seems to mesure faster
v = _mm_mulhi_epu16(v, mul1);
u = _mm_mullo_epi16(u, mul0);
v = _mm_mullo_epi16(v, mul0);
// write output
_mm_store_si128((__m128i*)(out ), u);
_mm_store_si128((__m128i*)(out + 16), v);
in += 16;
out += 32;
while (in != past);
void ExpandSSE2Unroll(unsigned char const *in, unsigned char const *past, unsigned char *out)
__m128i const mask = _mm_set1_epi16((short)0xF00F),
mul0 = _mm_set1_epi16(0x0011),
mul1 = _mm_set1_epi16(0x1000);
__m128i u0, v0,
u1, v1;
do
// Read input into low 8 bytes of u and v
u0 = _mm_load_si128((__m128i const*)(in ));
u1 = _mm_load_si128((__m128i const*)(in + 16));
v0 = _mm_unpackhi_epi8(u0, u0); // Expand each single byte to two bytes
u0 = _mm_unpacklo_epi8(u0, u0); // Do it again for v
v1 = _mm_unpackhi_epi8(u1, u1); // Do it again
u1 = _mm_unpacklo_epi8(u1, u1); // Again for u1
u0 = _mm_and_si128(u0, mask);
v0 = _mm_and_si128(v0, mask);
u1 = _mm_and_si128(u1, mask);
v1 = _mm_and_si128(v1, mask);
u0 = _mm_mullo_epi16(u0, mul0);
v0 = _mm_mullo_epi16(v0, mul0);
u1 = _mm_mullo_epi16(u1, mul0);
v1 = _mm_mullo_epi16(v1, mul0);
u0 = _mm_mulhi_epu16(u0, mul1);
v0 = _mm_mulhi_epu16(v0, mul1);
u1 = _mm_mulhi_epu16(u1, mul1);
v1 = _mm_mulhi_epu16(v1, mul1);
u0 = _mm_mullo_epi16(u0, mul0);
v0 = _mm_mullo_epi16(v0, mul0);
u1 = _mm_mullo_epi16(u1, mul0);
v1 = _mm_mullo_epi16(v1, mul0);
// write output
_mm_store_si128((__m128i*)(out ), u0);
_mm_store_si128((__m128i*)(out + 16), v0);
_mm_store_si128((__m128i*)(out + 32), u1);
_mm_store_si128((__m128i*)(out + 48), v1);
in += 32;
out += 64;
while (in != past);
void ExpandAShellySSE4(unsigned char const *in, unsigned char const *past, unsigned char *out)
__m128i const zero = _mm_setzero_si128(),
v0F0F = _mm_set1_epi32(0x0F0F),
vF0F0 = _mm_set1_epi32(0xF0F0),
v0101 = _mm_set1_epi32(0x0101),
v1010 = _mm_set1_epi32(0x1010),
v000F000F = _mm_set1_epi32(0x000F000F),
v0F000F00 = _mm_set1_epi32(0x0F000F00),
v0011 = _mm_set1_epi32(0x0011);
__m128i u, v, w, x;
do
// Read in data
u = _mm_load_si128((__m128i const*)in);
v = _mm_unpackhi_epi16(u, zero);
u = _mm_unpacklo_epi16(u, zero);
// original source: ((((a & 0xF0F) * 0x101) & 0xF000F) + (((a & 0xF0F0) * 0x1010) & 0xF000F00)) * 0x11;
w = _mm_and_si128(u, v0F0F);
x = _mm_and_si128(v, v0F0F);
u = _mm_and_si128(u, vF0F0);
v = _mm_and_si128(v, vF0F0);
w = _mm_mullo_epi32(w, v0101); // _mm_mullo_epi32 is what makes this require SSE4 instead of SSE2
x = _mm_mullo_epi32(x, v0101);
u = _mm_mullo_epi32(u, v1010);
v = _mm_mullo_epi32(v, v1010);
w = _mm_and_si128(w, v000F000F);
x = _mm_and_si128(x, v000F000F);
u = _mm_and_si128(u, v0F000F00);
v = _mm_and_si128(v, v0F000F00);
u = _mm_add_epi32(u, w);
v = _mm_add_epi32(v, x);
u = _mm_mullo_epi32(u, v0011);
v = _mm_mullo_epi32(v, v0011);
// write output
_mm_store_si128((__m128i*)(out ), u);
_mm_store_si128((__m128i*)(out + 16), v);
in += 16;
out += 32;
while (in != past);
int main()
unsigned char *const indat = new unsigned char[DATA_SIZE_IN ],
*const outdat0 = new unsigned char[DATA_SIZE_OUT],
*const outdat1 = new unsigned char[DATA_SIZE_OUT],
* curout = outdat0,
* lastout = outdat1,
* place;
unsigned start,
stop;
place = indat + DATA_SIZE_IN - 1;
do
*place = (unsigned char)rand();
while (place-- != indat);
MakeLutLo();
MakeLutHi();
MakeLutLarge();
for (unsigned testcount = 0; testcount < 1000; ++testcount)
// Solution posted by the asker
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandOrig(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandOrig:\t\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
// Dmitry's small lookup table solution
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandLookupSmall(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandSmallLUT:\t\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// Dmitry's small lookup table solution using only one lookup table
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandLookupSmallOneLUT(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandLookupSmallOneLUT:\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// Large lookup table solution
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandLookupLarge(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandLookupLarge:\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// AShelly's Interleave bits by Binary Magic Numbers solution
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandAShelly(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandAShelly:\t\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// AShelly's Interleave bits by Binary Magic Numbers solution optimizing out an addition
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandAShellyMulOp(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandAShellyMulOp:\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// My SSE4 solution
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandSSE4(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandSSE4:\t\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// My SSE4 solution unrolled
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandSSE4Unroll(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandSSE4Unroll:\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// My SSE2 solution
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandSSE2(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandSSE2:\t\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// My SSE2 solution unrolled
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandSSE2Unroll(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandSSE2Unroll:\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
// AShelly's Interleave bits by Binary Magic Numbers solution implemented using SSE2
start = clock();
for (unsigned rerun = 0; rerun < RERUN_COUNT; ++rerun)
ExpandAShellySSE4(indat, indat + DATA_SIZE_IN, curout);
stop = clock();
std::cout << "ExpandAShellySSE4:\t\t" << (((stop - start) / 1000) / 60) << ':' << (((stop - start) / 1000) % 60) << ":." << ((stop - start) % 1000) << std::endl;
std::swap(curout, lastout);
if (memcmp(outdat0, outdat1, DATA_SIZE_OUT))
std::cout << "INCORRECT OUTPUT" << std::endl;
delete[] indat;
delete[] outdat0;
delete[] outdat1;
return 0;
注意:
我最初在这里有一个 SSE4 实现。我找到了一种使用 SSE2 实现这一点的方法,这种方法更好,因为它可以在更多平台上运行。 SSE2 实现也更快。因此,顶部提出的解决方案现在是 SSE2 实现,而不是 SSE4 实现。在性能测试或编辑历史记录中仍然可以看到 SSE4 实现。
【讨论】:
干得好。感谢您进行测试。如果我从他们那里学到了一件事,那就是:我不想编写 SSE 代码,除非我绝对必须拥有最后一点性能:) 这肯定不容易阅读。 @AShelly:没有人这样做,尤其是在考虑可移植性的情况下。有多种 SIMD 指令集... SSE 仅在 x86 世界中有效。您必须处理 PPC 上的 AltiVec (VMX) 和 ARM 上的 NEON 等等。直接与指令集扩展接口在任何架构上都是丑陋的;) @AShelly 谢谢。一旦你开始记住一些操作是什么以及它们的命名约定,SSE 就不会那么糟糕了。几个月前我才真正开始使用它。当处理向量元素的排序变得更加第二天性时,它也有帮助。小型 LUT 也是一个非常快速的解决方案。有一个矢量化查找 _mm_i32gather_epi32,它将并行执行 4 次查找(AVX 是 SSE 的继承者)。看看涉及此的解决方案如何执行会很有趣。但是它需要 AVX2 指令,而且我还没有使用新的处理器。 我在我的系统上运行了你的性能测试(后来的 Core 2、64 位 linux、gcc 4.8.1 使用 -O3 -march=native)。虽然您的 SSE4 代码是最快的,但许多位操作解决方案几乎一样快(在百分之几内)......显然 gcc 在启用相关功能和优化时可以合理地自行对代码进行矢量化。 @Dmitri 很高兴知道编译器在自动矢量化方面做得越来越好。然而,我想编译器很难从通过 C++ 提供的相对有限的一组操作中确定你的算法实际上试图在高层次上完成什么,以便在整体上更有效地利用 shuffle/blend/unpack/ect 操作方式。相反,我猜它会尝试以矢量化方式模仿 C++ 代码,类似于我在 AShelly 的解决方案中包含的 SSE 实现,但这只是一个猜测。【参考方案2】:我不确定最有效的方法是什么,但这有点短:
#include <stdio.h>
int main()
unsigned x = 0x1234;
x = (x << 8) | x;
x = ((x & 0x00f000f0) << 4) | (x & 0x000f000f);
x = (x << 4) | x;
printf("0x1234 -> 0x%08x\n",x);
return 0;
如果您需要按照编辑中的建议反复快速地执行此操作,您可以考虑生成一个查找表并改用它。下面的函数动态分配和初始化这样一个表:
unsigned *makeLookupTable(void)
unsigned *tbl = malloc(sizeof(unsigned) * 65536);
if (!tbl) return NULL;
int i;
for (i = 0; i < 65536; i++)
unsigned x = i;
x |= (x << 8);
x = ((x & 0x00f000f0) << 4) | (x & 0x000f000f);
x |= (x << 4);
/* Uncomment next line to invert the high byte as mentioned in the edit. */
/* x = x ^ 0xff000000; */
tbl[i] = x;
return tbl;
之后每次转换都是这样的:
result = lookuptable[input];
..或者也许:
result = lookuptable[input & 0xffff];
或者可以使用一个更小、更缓存友好的查找表(或一对),每个查找一个查找高字节和低字节(如 cmets 中的 @LưuVĩnhPhúc 所述)。在这种情况下,表格生成代码可能是:
unsigned *makeLookupTableLow(void)
unsigned *tbl = malloc(sizeof(unsigned) * 256);
if (!tbl) return NULL;
int i;
for (i = 0; i < 256; i++)
unsigned x = i;
x = ((x & 0xf0) << 4) | (x & 0x0f);
x |= (x << 4);
tbl[i] = x;
return tbl;
...还有一个可选的第二个表:
unsigned *makeLookupTableHigh(void)
unsigned *tbl = malloc(sizeof(unsigned) * 256);
if (!tbl) return NULL;
int i;
for (i = 0; i < 256; i++)
unsigned x = i;
x = ((x & 0xf0) << 20) | ((x & 0x0f) << 16);
x |= (x << 4);
/* uncomment next line to invert high byte */
/* x = x ^ 0xff000000; */
tbl[i] = x;
return tbl;
...并用两个表转换一个值:
result = hightable[input >> 8] | lowtable[input & 0xff];
...或者一个(只是上面的矮桌):
result = (lowtable[input >> 8] << 16) | lowtable[input & 0xff];
result ^= 0xff000000; /* to invert high byte */
如果值的上半部分(alpha?)没有太大变化,即使是单个大表也可能表现良好,因为连续查找在表中会更靠近。
我采用了@Apriori 发布的性能测试代码,进行了一些调整,并为他最初没有包含的其他响应添加了测试......然后用不同的设置编译了它的三个版本。一个是启用 SSE4.1 的 64 位代码,编译器可以利用 SSE 进行优化……然后是两个 32 位版本,一个有 SSE,一个没有。尽管所有三个都在同一个相当新的处理器上运行,但结果显示了最佳解决方案如何根据处理器功能而变化:
64b SSE4.1 32b SSE4.1 32b no SSE
-------------------------- ---------- ---------- ----------
ExpandOrig time: 3.502 s 3.501 s 6.260 s
ExpandLookupSmall time: 3.530 s 3.997 s 3.996 s
ExpandLookupLarge time: 3.434 s 3.419 s 3.427 s
ExpandIsalamon time: 3.654 s 3.673 s 8.870 s
ExpandIsalamonOpt time: 3.784 s 3.720 s 8.719 s
ExpandChronoKitsune time: 3.658 s 3.463 s 6.546 s
ExpandEvgenyKluev time: 6.790 s 7.697 s 13.383 s
ExpandIammilind time: 3.485 s 3.498 s 6.436 s
ExpandDmitri time: 3.457 s 3.477 s 5.461 s
ExpandNitish712 time: 3.574 s 3.800 s 6.789 s
ExpandAdamLiss time: 3.673 s 5.680 s 6.969 s
ExpandAShelly time: 3.524 s 4.295 s 5.867 s
ExpandAShellyMulOp time: 3.527 s 4.295 s 5.852 s
ExpandSSE4 time: 3.428 s
ExpandSSE4Unroll time: 3.333 s
ExpandSSE2 time: 3.392 s
ExpandSSE2Unroll time: 3.318 s
ExpandAShellySSE4 time: 3.392 s
可执行文件是在 64 位 Linux 上使用 gcc 4.8.1 编译的,分别使用 -m64 -O3 -march=core2 -msse4.1
、-m32 -O3 -march=core2 -msse4.1
和 -m32 -O3 -march=core2 -mno-sse
。 @Apriori 的 SSE 测试在 32 位构建中被省略(在启用 SSE 的 32 位上崩溃,并且显然在禁用 SSE 的情况下不起作用)。
所做的调整之一是使用实际图像数据而不是随机值(具有透明背景的对象的照片),这大大提高了大型查找表的性能,但对其他查找表几乎没有影响。
基本上,当 SSE 不可用(或未使用)时,查找表会以压倒性优势获胜……否则手动编码的 SSE 解决方案会获胜。然而,同样值得注意的是,当编译器可以使用 SSE 进行优化时,大多数位操作解决方案几乎与手动编码的 SSE 一样快——仍然更慢,但只是略微。
【讨论】:
与 OP 已经获得的相比,任何涉及 malloc 的东西都会非常低效。 @Lundin 这是设置表的一次性malloc,它不是转换的一部分。 @Lundin 它只需要在运行时执行一次...在程序启动时只需要几分之一秒。静态 const 表也可以,但您需要编写一个程序来根据表的大小生成源代码。 只需 256 个条目的查找表就足够了。您可以分别查找低字节和高字节。太大的表可能不适合缓存,性能可能会更差 @Apriori 图像数据在测试运行之前从单独的文件中加载。【参考方案3】:这是另一个尝试,使用了八个操作:
b = (((c & 0x0F0F) * 0x0101) & 0x00F000F) +
(((c & 0xF0F0) * 0x1010) & 0xF000F00);
b += b * 0x10;
printf("%x\n",b); //Shows '0x11223344'
*注意,这篇文章最初包含完全不同的代码,基于 Sean Anderson 的 bithacks 页面中的 Interleave bits by Binary Magic Numbers。但这并不是OP所要求的。所以它已被删除。下面的大部分cmets都引用了那个missing version。
【讨论】:
那里有一些非常丑陋的代码。无论结果多么有效,它看起来都像是过早的优化。为了代码的可读性、维护性和无错误,我会认真考虑使用效率较低的代码。 @Lundin 那边的代码已经调试好了,只要使用,如果有任何问题,它在你的代码中。你总是可以在代码中留下注释来描述函数的作用以及你从哪里得到它 @LưuVĩnhPhúc 事实上,任何错误都会出现在我的代码中。如果我的程序是一大堆幻数,你如何建议我顺利调试上述代码? 建议的代码将复制 8 位值的 单个位。而 OP 要求复制 16 位值的 4 位半字节。 你是绝对正确的@EvgenyKluev。这行不通。【参考方案4】:我想将此链接添加到答案池中,因为我认为在谈论优化时记住我们正在运行的硬件以及为所述平台编译我们的代码的技术非常重要。
博文 Playing with the CPU pipeline 是关于研究优化 CPU 流水线的一组代码。它实际上展示了一个示例,说明他试图将数学简化为最少的实际数学运算,但就时间而言,它与最佳解决方案相去甚远。我在这里看到了几个答案,它们可能是正确的,也可能不是。唯一知道的方法是与其他代码相比,实际测量您的特定 sn-p 代码从开始到结束的时间。阅读此博客;非常有趣。
我想我应该提一下,在这种特殊情况下,我不会在此处放置任何代码,除非我确实尝试过多次尝试,并且实际上通过多次尝试得到的速度特别快。
【讨论】:
虽然我同意你关于分析的必要性,但这个答案并不是真正的答案。 @AShelly - 我认为这里的其他人都在给出答案,但他们不知道。我试图说明的一点是,如果不为您的特定架构工作,您实际上不会知道。事实上,仅仅因为它是 x86 并不意味着它会是相同的。 AMD 可能与 intel 不同,实际上 i7 可能与 core 2 不同。简单地问什么是最快的,比代码本身要困难得多。【参考方案5】:我认为Dimitri建议的查表方式是一个不错的选择,但我建议更进一步,在编译时生成表;在编译时进行工作显然会减少执行时间。
首先,我们使用任何建议的方法创建一个编译时值:
constexpr unsigned int transform1(unsigned int x)
return ((x << 8) | x);
constexpr unsigned int transform2(unsigned int x)
return (((x & 0x00f000f0) << 4) | (x & 0x000f000f));
constexpr unsigned int transform3(unsigned int x)
return ((x << 4) | x);
constexpr unsigned int transform(unsigned int x)
return transform3(transform2(transform1(x)));
// Dimitri version, using constexprs
template <unsigned int argb> struct aarrggbb_dimitri
static const unsigned int value = transform(argb);
;
// Adam Liss version
template <unsigned int argb> struct aarrggbb_adamLiss
static const unsigned int value =
(argb & 0xf000) * 0x11000 +
(argb & 0x0f00) * 0x01100 +
(argb & 0x00f0) * 0x00110 +
(argb & 0x000f) * 0x00011;
;
然后,我们使用我们可用的任何方法创建编译时查找表,我希望使用 C++14 integer sequence 但我不知道 OP 将使用哪个编译器。所以另一种可能的方法是使用一个非常丑陋的宏:
#define EXPAND16(x) aarrggbb<x + 0>::value, \
aarrggbb<x + 1>::value, \
aarrggbb<x + 2>::value, \
aarrggbb<x + 3>::value, \
aarrggbb<x + 4>::value, \
aarrggbb<x + 5>::value, \
aarrggbb<x + 6>::value, \
... and so on
#define EXPAND EXPAND16(0), \
EXPAND16(0x10), \
EXPAND16(0x20), \
EXPAND16(0x30), \
EXPAND16(0x40), \
... and so on
... and so on
见demo here。
PS:Adam Liss 方法可以在没有 C++11 的情况下使用。
【讨论】:
【参考方案6】:如果乘法很便宜并且可以使用 64 位算术,您可以使用以下代码:
uint64_t x = 0x1234;
x *= 0x0001000100010001ull;
x &= 0xF0000F0000F0000Full;
x *= 0x0000001001001001ull;
x &= 0xF0F0F0F000000000ull;
x = (x >> 36) * 0x11;
std::cout << std::hex << x << '\n';
其实和AShelly最初尝试的思路是一样的。
【讨论】:
【参考方案7】:这很有效,可能更容易理解,但位操作非常便宜,我不会太担心效率。
#include <stdio.h>
#include <stdlib.h>
void main()
unsigned int c = 0x1234, b;
b = (c & 0xf000) * 0x11000 + (c & 0x0f00) * 0x01100 +
(c & 0x00f0) * 0x00110 + (c & 0x000f) * 0x00011;
printf("%x -> %x\n", c, b);
【讨论】:
我相信大多数架构中的乘法运算都是昂贵的。此外,它可能必须使用数学处理器。 这已经不像过去那么真实了。 试试这个:编写一个快速的 C 程序,将一个数字乘以 0x11。编写另一个将其向左移动 4 位并将结果添加到原始结果中。将它们编译为汇编并比较结果。 “信念”远不如验证重要,尤其是在现代编译器优化方面。 @AdamLiss 我想在这里补充一点,我非常相信验证胜过信念,但众所周知的事实是,无论是浮点数还是整数,乘法确实比加法慢。我在这里找到了software.intel.com/en-us/forums/topic/299987,在核心 2 中它仍然比加法或按位运算慢 3 倍。我认为 Adam 是说在这种情况下,编译器本质上会将乘法优化为加法或某些位运算符。那么这段代码确实可以快速且易于阅读。 重点是,根据被乘数的不同,编译器可能根本不会生成乘法指令。作为一名新程序员,我花了很长时间试图理解编译器为整数乘法n *= 10
生成的汇编代码。然后我意识到知道n * 10
= n * (2 + 8)
= (n << 1) + (n << 3)
是足够聪明的,它更喜欢更有效的转变,并增加了更简单但更昂贵的乘法。如果编译器没有实现3 * 2^n
(即0x11 << n
)的乘法转换和加法,我会感到惊讶。【参考方案8】:
假设您希望始终将0xWXYZ
转换为0xWWXXYYZZ
,我相信以下解决方案会比您建议的解决方案快一点:
unsigned int c = 0x1234;
unsigned int b = (c & 0xf) | ((c & 0xf0) << 4) |
((c & 0xf00) << 8) | ((c & 0xf000) << 12);
b |= (b << 4);
请注意,一个 &
(and
) 操作已从您的解决方案中保存。 :-)Demo.
【讨论】:
【参考方案9】:另一种方式是:
DWORD OrVal(DWORD & nible_pos, DWORD input_val, DWORD temp_val, int shift)
if (nible_pos==0)
nible_pos = 0x0000000F;
else
nible_pos = nible_pos << 4;
DWORD nible = input_val & nible_pos;
temp_val |= (nible << shift);
temp_val |= (nible << (shift + 4));
return temp_val;
DWORD Converter2(DWORD input_val)
DWORD nible_pos = 0x00000000;
DWORD temp_val = 0x00000000;
temp_val = OrVal(nible_pos, input_val, temp_val, 0);
temp_val = OrVal(nible_pos, input_val, temp_val, 4);
temp_val = OrVal(nible_pos, input_val, temp_val, 8);
temp_val = OrVal(nible_pos, input_val, temp_val, 12);
return temp_val;
DWORD val2 = Converter2(0x1234);
优化版本(快 3 倍):
DWORD 转换器3(DWORD input_val) DWORD nible_pos = 0; DWORD temp_val = 0; 整数移位 = 0; DWORD bit_nible[4] = 0x000F, 0x000F0, 0x0F00, 0xF000 ; for ( ; shift【讨论】:
【参考方案10】:也许这会更简单、更高效。
unsigned int g = 0x1234;
unsigned int ans = 0;
ans = ( ( g & 0xf000 ) << 16) + ( (g & 0xf00 ) << 12)
+ ( ( g&0xf0 ) << 8) + ( ( g&0xf ) << 4);
ans = ( ans | ans>>4 );
printf("%p -> %p\n", g, ans);
【讨论】:
这不会有效率。您使用的|
(或+
)、<<
(或>>
)数量与之前的解决方案相同。【参考方案11】:
unsigned long transform(unsigned long n)
/* n: 00AR
* 00GB
*/
n = ((n & 0xff00) << 8) | (n & 0x00ff);
/* n: 0AR0
* 0GB0
*/
n <<= 4;
/* n: AAR0
* GGB0
*/
n |= (n & 0x0f000f00L) << 4;
/* n: AARR
* GGBB
*/
n |= (n & 0x00f000f0L) >> 4;
return n;
alpha 和 red 分量被移到它们所属的高 2 个字节中,然后将结果左移 4 位,从而使每个分量都准确地位于它需要的位置。
对于 0AR0 0GB0 的形式,位掩码和左移组合与当前值进行“或”运算。这会将 A 和 G 组件复制到它们左侧的位置。对 R 和 B 组件执行相同的操作,只是方向相反。
【讨论】:
【参考方案12】:如果您要为OpenGL 执行此操作,我建议您使用glTexImageXD
函数并将type
参数设置为GL_UNSIGNED_SHORT_4_4_4_4
。您的 OpenGL 驱动程序应该完成其余的工作。关于透明度反转,您始终可以通过 glBlendFunc
和 glBlendEquation
函数来操作混合。
【讨论】:
这应该被标记为“已接受的答案”,因为这是我需要的,但问题的原始表述是关于数字的。【参考方案13】:而其他人则在进行硬核优化...
将此作为您的最佳选择:
std::string toAARRGGBB(const std::string &argb)
std::string ret("0x");
int start = 2; //"0x####";
// ^^ skipped
for (int i = start;i < argb.length(); ++i)
ret += argb[i];
ret += argb[i];
return ret;
int main()
std::string argb = toAARRGGBB("0xACED"); //!!!
哈哈
【讨论】:
问题不是将数字转换为字符串——而是通过复制半字节将一个数字更改为不同的数字。我认为您的回答完全没有抓住重点。 @mr5,因为我们爱他并了解他♥以上是关于将 0x1234 转换为 0x11223344的主要内容,如果未能解决你的问题,请参考以下文章
将rowdatapacket转换为数组,如何将mysql node.js api rowdatapacket转换为数组,将字符串转换为数组