web-dev-qa-db-ja.com

charより大きい整数を受け入れるmemset()はありますか?

1バイト(char)より大きい値を設定するmemset()のバージョンはありますか?たとえば、memset32()関数があるとすると、それを使用して次のことができます。

int32_t array[10];
memset32(array, 0xDEADBEEF, sizeof(array));

これにより、配列のすべての要素に値0xDEADBEEFが設定されます。現在、これはループでのみ実行できるようです。

具体的には、64ビットバージョンのmemset()に興味があります。そのようなことを知っていますか?

31
gnobal
void memset64( void * dest, uint64_t value, uintptr_t size )
{
  uintptr_t i;
  for( i = 0; i < (size & (~7)); i+=8 )
  {
    memcpy( ((char*)dest) + i, &value, 8 );
  }  
  for( ; i < size; i++ )
  {
    ((char*)dest)[i] = ((char*)&value)[i&7];
  }  
}

(説明、コメントで要求されているように:ポインターに割り当てると、コンパイラーはポインターが型の自然な配置に揃えられていると想定します。uint64_tの場合、つまり8バイトです。memcpy()はそのような想定を行いません。一部のハードウェアでは調整されていません。アクセスは不可能であるため、アラインされていないアクセスがハードウェア上でペナルティがほとんどまたはまったくないか、発生しないこと、あるいはその両方がないことを知らない限り、割り当ては適切な解決策ではありません。コンパイラは小さなmemcpy()とmemset()を置き換えます。 sより適切なコードを使用しているため、見た目ほどひどいものではありません。ただし、割り当てが常に機能することを保証するのに十分な知識があり、プロファイラーから高速であると通知された場合は、memcpyを割り当てに置き換えることができます。2番目のfor()満たされるメモリの量が64ビットの倍数でない場合、ループが存在します。常にそうなることがわかっている場合は、単にそのループを削除できます。)

31
moonshadow

標準ライブラリ関数afaikはありません。したがって、ポータブルコードを作成している場合は、ループを見ていることになります。

移植性のないコードを書いている場合は、コンパイラ/プラットフォームのドキュメントを確認してください。ただし、ここで多くのヘルプが得られることはめったにないため、息を止めないでください。たぶん、他の誰かが何かを提供するプラットフォームの例をチップインするでしょう。

独自に作成する方法は、APIで、dstポインターがプラットフォーム(または移植可能な場合はプラットフォーム)での64ビット書き込みに対して十分に整列されることを呼び出し元が保証することを定義できるかどうかによって異なります。 64ビット整数型のプラットフォームでは、mallocは少なくとも適切に配置されたポインターを返します。

非同盟に対処する必要がある場合は、moonshadowの答えのようなものが必要です。コンパイラーはそのmemcpyをサイズ8でインライン化/展開する可能性があるため(存在する場合は32ビットまたは64ビットの非整列書き込み操作を使用します)、コードはかなりニッピーになるはずですが、おそらく特別な場合ではないと思います調整される宛先の関数全体。私は訂正されたいのですが、訂正されないのではないかと恐れています。

したがって、呼び出し元が常にアーキテクチャに十分なアラインメントを備えたdstを提供し、長さが8バイトの倍数であることがわかっている場合は、uint64_t(または64ビットintが含まれているもの)を書き込む単純なループを実行します。コンパイラ)そしてあなたはおそらく(約束なしで)より速いコードで終わるでしょう。あなたは確かに短いコードを持っているでしょう。

いずれにせよ、パフォーマンスを気にする場合は、プロファイルを作成してください。十分に速くない場合は、さらに最適化して再試行してください。それでも十分な速度が得られない場合は、十分な速度が得られていないCPUのasmバージョンについて質問してください。 memcpy/memsetは、プラットフォームごとの最適化によってパフォーマンスを大幅に向上させることができます。

10
Steve Jessop

念のため、以下はmemcpy(..)を次のパターンで使用します。配列を20個の整数で埋めたいとします。

_--------------------

First copy one:
N-------------------

Then copy it to the neighbour:
NN------------------

Then copy them to make four:
NNNN----------------

And so on:
NNNNNNNN------------

NNNNNNNNNNNNNNNN----

Then copy enough to fill the array:
NNNNNNNNNNNNNNNNNNNN
_

これには、O(lg(num)) memcpy(..)のアプリケーションが必要です。

_int *memset_int(int *ptr, int value, size_t num) {
    if (num < 1) return ptr;
    memcpy(ptr, &value, sizeof(int));
    size_t start = 1, step = 1;
    for ( ; start + step <= num; start += step, step *= 2)
        memcpy(ptr + start, ptr, sizeof(int) * step);

    if (start < num)
        memcpy(ptr + start, ptr, sizeof(int) * (num - start));
    return ptr;
}
_

memcpy(..)がハードウェアブロックメモリコピー機能を使用して最適化されている場合、ループよりも高速になる可能性があると思いましたが、単純なループは-O2と-O3を使用した場合よりも高速であることがわかりました。 (少なくとも、特定のハードウェアを搭載したWindowsでMinGW GCCを使用します。)-Oスイッチがない場合、400 MBアレイでは、上記のコードは同等のループの約2倍の速度で、マシンでは417ミリ秒かかりますが、最適化すると両方とも約300ミリ秒になります。つまり、バイトとほぼ同じナノ秒数がかかり、クロックサイクルは約ナノ秒です。したがって、私のマシンにハードウェアブロックメモリコピー機能がないか、memcpy(..)実装がそれを利用していません。

7
Evgeni Sergeev

OSのドキュメントでローカルバージョンを確認してから、ループの使用を検討してください。

コンパイラはおそらく、特定のアーキテクチャでのメモリアクセスの最適化について、あなたよりもよく知っているので、作業を任せてください。

それをライブラリとしてラップし、コンパイラが許可するすべての速度向上最適化を使用してコンパイルします。

6
dmckee

wmemset(3)は、memsetのワイド(16ビット)バージョンです。ループなしで、Cで取得するのに最も近いと思います。

4
Alex M

X86コンパイラをターゲットにしているだけの場合は、次のようなものを試すことができます(VC++の例)。

inline void memset32(void *buf, uint32_t n, int32_t c)
{
  __asm {
  mov ecx, n
  mov eax, c
  mov edi, buf
  rep stosd
  }
}

それ以外の場合は、単純なループを作成し、オプティマイザーが何をしているのかを信頼して、次のようにします。

for(uint32_t i = 0;i < n;i++)
{
  ((int_32 *)buf)[i] = c;
}

複雑な可能性を持たせると、保守が難しくなることは言うまでもなく、コードを最適化するのが簡単になるよりも遅くなる可能性があります。

2
Cosmin

他の誰かが提案したように、コンパイラにこれを最適化させる必要があります。ほとんどの場合、そのループはごくわずかです。

しかし、これが特別な状況であり、プラットフォーム固有であることを気にせず、本当にループを取り除く必要がある場合は、Assemblyブロックでこれを行うことができます。

//pseudo code
asm
{
    rep stosq ...
}

あなたはおそらく詳細のためにstosqアセンブリコマンドをグーグルすることができます。数行を超えるコードであってはなりません。

1
kervin

あなた自身を書いてください。 asmでもささいなことです。

0
Kevin Conner