2016-05-07 6 views
4

、私は32ビットリトルエンディアンのプロセッサを使用していて、次の4バイトのバッファを宣言していると仮定します。32/64ビットでバイトを効率的にビットシフトしますか?簡単にするために

unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 }; 

のが私の目標は、バッファ内のビット単位の左シフトに各バイトであるとしましょう4ビット分。つまり、バッファ値を { 0xbc, 0xde, 0xf4, 0x60 }に変換したいとします。これは動作しますが、私はむしろ私のプロセッサのネイティブ32ビットレジスタを使用して、同時にすべての4つのバイトをシフトしたい

for (int i = 0; i < 3; ++i) 
{ 
    buffer[i] <<= 4; 
    buffer[i] |= (buffer[i + 1] >> 4); 
} 
buffer[3] <<= 4; 

::のように、このような変換を実行するためには、コードを書くかもしれません

unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 }; 
unsigned int *p = (unsigned int*)buffer; // unsigned int is 32 bit on my platform 
*p <<= 4; 

ザ・上のスニペットは正常にシフトを実行しますが、私が探している方法ではありません。 unsigned intにバッファをキャストしているので、レジスタは0x46efcdab0xabcdef46ではなく)の値(リトルエンディアン)でロードされているようです。したがって、左ビットシフトを4ビット実行すると、0xbcdef460の代わりに0xb0dafc6eになります。

シフト(たとえばhtonlなど)の前にバイトをスワップする以外に、効率的にバイトをシフトさせる方法がありますか?

あなたの洞察を前もってありがとうございます。

+0

@ user3386109、はいそれはx86です – digitale

+1

私はnneonneoの答えに同意します。コンパイラは 'htonl'を単一の命令として(適切な最適化レベルで)実装します。最小値は何か分かりませんが、 '-O3'は間違いなく動作します。 – user3386109

+0

C言語が提供するfixed型を使用した場合、プラットフォーム上に 'unsigned'がどのくらい広まっているか、さまざまな仮定をすることができます。' stdint.h'を参照してください。そして、データをシリアライズしたい場合は、キャストではなく適切なビットシフト/マスキングを使用してください。 – Olaf

答えて

6

htonl/ntohlネットワーク(ビッグエンディアン)のバイト順とネイティブバイト順の間フリップする使用:

効果で
uint32_t *p = (uint32_t*)buffer; 
*p = htonl(ntohl(*p) << 4); 

が、これはビッグエンディアンの整数でバッファの内容をロードします順序を変更してから、ビッグエンディアンの順序で書き戻します。

これはx86上のbswap命令の2つにコンパイルされるため、合理的に効率的でなければなりません(gcc -O3)。


ここではいくつかのテストコード(buffer定折りたたみを避けるためにグローバルであり、returnはデッドコードの除去を防止する)です:

#include <stdint.h> // uint32_t 
#include <arpa/inet.h> // ntohl, htonl 

unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 }; 

int main() { 
    uint32_t *p = (uint32_t*)buffer; // unsigned int is 32 bit on my platform 
    *p = htonl(ntohl(*p) << 4); 
    return *p; 
} 

これは、次のかなり単純なマシンコードにコンパイルする(x86ベースの64; LLVM 7.0.2; cc -O2):

0000000000000000 pushq %rbp   ; frame setup 
0000000000000001 movq %rsp, %rbp  ; frame setup 
0000000000000004 movl (%rip), %eax ; load buffer 
000000000000000a bswapl %eax   ; endian flip 
000000000000000c shll $0x4, %eax  ; shift 
000000000000000f bswapl %eax   ; endian flip 
0000000000000011 movl %eax, (%rip) ; save buffer 
0000000000000017 popq %rbp   ; finish 
0000000000000018 retq 
+0

'march'オプション(atomやhaswellのような)が十分にある場合、' mov + bswap'の代わりに 'movbe'を発行します –

+0

htonl/ntohlが実際に呼び出し命令を呼び出さないことを教えてくれてありがとう – digitale

3

ただ、比較のために、あなたはhtonl/0123を使用せずにこれを行うことができます。 gcc -O3

#include <stdint.h> 

void lshift(unsigned char* buf) { 
    uint32_t* p = (uint32_t*)buf; 
    uint32_t lo = *p & 0x0F0F0F0F; 
    uint32_t hi = *p & 0xF0F0F000; 
    *p = (lo << 4) | (hi >> 12); 
} 

そして、生成されたアセンブリ::これはリトルエンディアンCPUを想定してい

pushq %rbp 
movq %rsp, %rbp 
movl (%rdi), %eax 
movl %eax, %ecx 
shll $4, %ecx 
andl $-252645136, %ecx  ## imm = 0xFFFFFFFFF0F0F0F0 
shrl $12, %eax 
andl $986895, %eax   ## imm = 0xF0F0F 
orl  %ecx, %eax 
movl %eax, (%rdi) 
popq %rbp 
retq 

bswaplがどのように多くのサイクルに応じて、それは可能性が速く、代替です。

+0

ビット単位の演算子しか使用しません – digitale

関連する問題