byte[]
に1つのnon-zero値を入力する必要があります。配列内の各byte
をループせずにC#でこれを行うにはどうすればよいですか?
更新:コメントはこれを2つの質問に分けているようです-
memset
に似ている可能性のあるbyte []を埋めるためのFrameworkメソッドはありますかエリックや他の人が指摘したように、単純なループを使用しても問題なく動作することに完全に同意します。質問のポイントは、C#について何か新しいことを学ぶことができるかどうかを確認することでした:)ジュリエットのParallel操作の方法は、単純なループよりもさらに高速である必要があると思います。
ベンチマーク:Mikael Svensonに感謝: http://techmikael.blogspot.com/2009/12/filling-array-with-default -value.html
安全でないコードを使用したくない限り、単純なfor
ループが道であることがわかります。
私の元の投稿で明確になっていないことをおologiesびします。エリックとマークはどちらもコメントで正しいです。より確実な質問をする必要があります。皆の提案と応答をありがとう。
byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();
最初のパラメーターは繰り返したい要素であり、2番目のパラメーターはそれを繰り返す回数です。
これは小さな配列では問題ありませんが、非常に大きな配列を処理していてパフォーマンスが懸念される場合は、ループ方法を使用する必要があります。
実際、 Initblk ( 英語版 )と呼ばれる既知のIL操作はほとんどありません。それで、「安全でない」ことを必要としない方法としてそれを使用しましょう。ヘルパークラスは次のとおりです。
public static class Util
{
static Util()
{
var dynamicMethod = new DynamicMethod("Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
null, new [] { typeof(IntPtr), typeof(byte), typeof(int) }, typeof(Util), true);
var generator = dynamicMethod.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Ldarg_2);
generator.Emit(OpCodes.Initblk);
generator.Emit(OpCodes.Ret);
MemsetDelegate = (Action<IntPtr, byte, int>)dynamicMethod.CreateDelegate(typeof(Action<IntPtr, byte, int>));
}
public static void Memset(byte[] array, byte what, int length)
{
var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, length);
gcHandle.Free();
}
public static void ForMemset(byte[] array, byte what, int length)
{
for(var i = 0; i < length; i++)
{
array[i] = what;
}
}
private static Action<IntPtr, byte, int> MemsetDelegate;
}
そして、パフォーマンスは何ですか?これは、Windows/.NETとLinux/Mono(異なるPC)に対する私の結果です。
Mono/for: 00:00:01.1356610
Mono/initblk: 00:00:00.2385835
.NET/for: 00:00:01.7463579
.NET/initblk: 00:00:00.5953503
したがって、検討する価値があります。結果のILは検証できないことに注意してください。
少し遅れましたが、次のアプローチは安全でないコードに戻すことなく適切に妥協することができます。基本的には、従来のループを使用して配列の先頭を初期化し、Buffer.BlockCopy()
に戻ります。これは、マネージドコールを使用して取得できる限り高速になります。
public static void MemSet(byte[] array, byte value) {
if (array == null) {
throw new ArgumentNullException("array");
}
const int blockSize = 4096; // bigger may be better to a certain extent
int index = 0;
int length = Math.Min(blockSize, array.Length);
while (index < length) {
array[index++] = value;
}
length = array.Length;
while (index < length) {
Buffer.BlockCopy(array, 0, array, index, Math.Min(blockSize, length-index));
index += blockSize;
}
}
Luceroの答え に基づいて作成した、こちらがより高速なバージョンです。繰り返しごとにBuffer.BlockCopy
を使用してコピーされるバイト数を2倍にします。興味深いことに、比較的小さな配列(1000)を使用する場合は10倍の性能を発揮しますが、大きな配列(1000000)の場合はそれほど大きくはありませんが、常に高速です。それについての良いところは、小さな配列までうまく動作することです。 length = 100付近で単純なアプローチよりも高速になります。100万要素のバイト配列の場合、43倍高速でした。 (Intel i7、.Net 2.0でテスト済み)
public static void MemSet(byte[] array, byte value) {
if (array == null) {
throw new ArgumentNullException("array");
}
int block = 32, index = 0;
int length = Math.Min(block, array.Length);
//Fill the initial array
while (index < length) {
array[index++] = value;
}
length = array.Length;
while (index < length) {
Buffer.BlockCopy(array, 0, array, index, Math.Min(block, length-index));
index += block;
block *= 2;
}
}
この単純な実装は、連続的な倍増を使用し、非常によく機能します(私のベンチマークによると、ナイーブバージョンよりも約3〜4倍高速です)。
public static void Memset<T>(T[] array, T elem)
{
int length = array.Length;
if (length == 0) return;
array[0] = elem;
int count;
for (count = 1; count <= length/2; count*=2)
Array.Copy(array, 0, array, count, count);
Array.Copy(array, 0, array, count, length - count);
}
編集:他の答えを読んで、この考えを持つ唯一の人ではないようです。それでも、私はこれをここに残しています、それは少しきれいで、他と同等に動作するからです。
パフォーマンスが重要な場合は、安全でないコードを使用して、配列へのポインターを直接操作することを検討できます。
別のオプションは、msvcrt.dllからmemsetをインポートして使用することです。ただし、呼び出しによるオーバーヘッドは、速度の向上よりも簡単に大きくなる可能性があります。
パフォーマンスが絶対に重要な場合、Enumerable.Repeat(n, m).ToArray()
はニーズに対して遅すぎます。 PLINQまたは Task Parallel Library を使用して、より高速なパフォーマンスを実現できる場合があります。
using System.Threading.Tasks;
// ...
byte initialValue = 20;
byte[] data = new byte[size]
Parallel.For(0, size, index => data[index] = initialValue);
すべての答えはシングルバイトのみを書いています-バイト配列を単語で埋めたい場合はどうしますか?または浮く?私は時々そのために使用を見つけます。したがって、「memset」に同様のコードを一般的でない方法で数回記述し、このページに到達して単一バイトの適切なコードを見つけた後、以下のメソッドを記述しました。
PInvokeとC++/CLIにはそれぞれ欠点があると思います。そして、mscorxxxにランタイム「PInvoke」を用意してみませんか? Array.CopyとBuffer.BlockCopyは、ネイティブコードです。 BlockCopyは「安全」ではありません-配列の中にある限り、長い間を別のコピーの上に、またはDateTimeの上にコピーできます。
少なくとも、私はこのようなことのために新しいC++プロジェクトをファイルするつもりはありません-それはほとんど間違いなく時間の無駄です。
したがって、基本的には、LuceroおよびTowerOfBricksが提供するソリューションの拡張バージョンで、memset long、intなど、およびシングルバイトに使用できます。
public static class MemsetExtensions
{
static void MemsetPrivate(this byte[] buffer, byte[] value, int offset, int length) {
var shift = 0;
for (; shift < 32; shift++)
if (value.Length == 1 << shift)
break;
if (shift == 32 || value.Length != 1 << shift)
throw new ArgumentException(
"The source array must have a length that is a power of two and be shorter than 4GB.", "value");
int remainder;
int count = Math.DivRem(length, value.Length, out remainder);
var si = 0;
var di = offset;
int cx;
if (count < 1)
cx = remainder;
else
cx = value.Length;
Buffer.BlockCopy(value, si, buffer, di, cx);
if (cx == remainder)
return;
var cachetrash = Math.Max(12, shift); // 1 << 12 == 4096
si = di;
di += cx;
var dx = offset + length;
// doubling up to 1 << cachetrash bytes i.e. 2^12 or value.Length whichever is larger
for (var al = shift; al <= cachetrash && di + (cx = 1 << al) < dx; al++) {
Buffer.BlockCopy(buffer, si, buffer, di, cx);
di += cx;
}
// cx bytes as long as it fits
for (; di + cx <= dx; di += cx)
Buffer.BlockCopy(buffer, si, buffer, di, cx);
// tail part if less than cx bytes
if (di < dx)
Buffer.BlockCopy(buffer, si, buffer, di, dx - di);
}
}
これにより、memsetに必要な値の型を取得するための短いメソッドを追加して、プライベートメソッドを呼び出すことができます。このメソッドでulongの置換を見つけるだけです:
public static void Memset(this byte[] buffer, ulong value, int offset, int count) {
var sourceArray = BitConverter.GetBytes(value);
MemsetPrivate(buffer, sourceArray, offset, sizeof(ulong) * count);
}
または、愚かにして、あらゆるタイプの構造体でそれを行います(ただし、上記のMemsetPrivateは、2の累乗のサイズにマーシャリングする構造体に対してのみ機能します)。
public static void Memset<T>(this byte[] buffer, T value, int offset, int count) where T : struct {
var size = Marshal.SizeOf<T>();
var ptr = Marshal.AllocHGlobal(size);
var sourceArray = new byte[size];
try {
Marshal.StructureToPtr<T>(value, ptr, false);
Marshal.Copy(ptr, sourceArray, 0, size);
} finally {
Marshal.FreeHGlobal(ptr);
}
MemsetPrivate(buffer, sourceArray, offset, count * size);
}
前に述べたinitblkを変更して、パフォーマンスをコードと比較するためにulongを使用しましたが、それは静かに失敗します-コードは実行されますが、結果のバッファーにはulongの最下位バイトのみが含まれます。
それにもかかわらず、私はfor、initblkおよびmemsetメソッドを使用して、大きなバッファとしての書き込みパフォーマンスを比較しました。時間はミリ秒単位で合計100回の繰り返しで、バッファ長に適合する回数に関係なく8バイトのulongを書き込みます。 forバージョンは、単一のulongの8バイトに対して手動でループ展開されます。
Buffer Len #repeat For millisec Initblk millisec Memset millisec
0x00000008 100 For 0,0032 Initblk 0,0107 Memset 0,0052
0x00000010 100 For 0,0037 Initblk 0,0102 Memset 0,0039
0x00000020 100 For 0,0032 Initblk 0,0106 Memset 0,0050
0x00000040 100 For 0,0053 Initblk 0,0121 Memset 0,0106
0x00000080 100 For 0,0097 Initblk 0,0121 Memset 0,0091
0x00000100 100 For 0,0179 Initblk 0,0122 Memset 0,0102
0x00000200 100 For 0,0384 Initblk 0,0123 Memset 0,0126
0x00000400 100 For 0,0789 Initblk 0,0130 Memset 0,0189
0x00000800 100 For 0,1357 Initblk 0,0153 Memset 0,0170
0x00001000 100 For 0,2811 Initblk 0,0167 Memset 0,0221
0x00002000 100 For 0,5519 Initblk 0,0278 Memset 0,0274
0x00004000 100 For 1,1100 Initblk 0,0329 Memset 0,0383
0x00008000 100 For 2,2332 Initblk 0,0827 Memset 0,0864
0x00010000 100 For 4,4407 Initblk 0,1551 Memset 0,1602
0x00020000 100 For 9,1331 Initblk 0,2768 Memset 0,3044
0x00040000 100 For 18,2497 Initblk 0,5500 Memset 0,5901
0x00080000 100 For 35,8650 Initblk 1,1236 Memset 1,5762
0x00100000 100 For 71,6806 Initblk 2,2836 Memset 3,2323
0x00200000 100 For 77,8086 Initblk 2,1991 Memset 3,0144
0x00400000 100 For 131,2923 Initblk 4,7837 Memset 6,8505
0x00800000 100 For 263,2917 Initblk 16,1354 Memset 33,3719
Initblkとmemsetの両方がヒットするため、毎回最初の呼び出しを除外しました。最初の呼び出しでは約.22msだったと思います。私のコードは、initblkよりも短いバッファーを埋める方が高速であり、セットアップコードで半分のページがいっぱいになっていることがわかります。
誰かがこれを最適化したいと思うなら、本当に先に進んでください。それが可能だ。
[DllImport("msvcrt.dll",
EntryPoint = "memset",
CallingConvention = CallingConvention.Cdecl,
SetLastError = false)]
public static extern IntPtr MemSet(IntPtr dest, int c, int count);
static void Main(string[] args)
{
byte[] arr = new byte[3];
GCHandle gch = GCHandle.Alloc(arr, GCHandleType.Pinned);
MemSet(gch.AddrOfPinnedObject(), 0x7, arr.Length);
}
配列を初期化するときにそれを行うことができますが、私はそれがあなたが求めているものだとは思わない:
byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};
System.Runtime.CompilerServices.Unsafe.InitBlock
は、Konradの答えが言及するOpCodes.Initblk
命令と同じことをするようになりました(彼は ソースリンク も言及しました)。
配列に入力するコードは次のとおりです。
byte[] a = new byte[N];
byte valueToFill = 255;
System.Runtime.CompilerServices.Unsafe.InitBlock(ref a[0], valueToFill, (uint) a.Length);
さまざまな回答で説明されているいくつかの方法をテストしました。 C#でテストのソースを参照してください テストクラス
.NET Coreには組み込みのArray.Fill()関数がありますが、残念ながら.NET Frameworkにはそれがありません。 .NET Coreには2つのバリエーションがあります。配列全体を埋める、およびインデックスから始まる配列の一部を埋めます。
上記のアイデアに基づいて、いくつかのデータ型の配列全体を埋めるより一般的なFill関数を次に示します。これは、この投稿で説明した他の方法と比較してベンチマークする場合の最速の機能です。
この関数は、配列の一部を埋めるバージョンとともに、オープンソースの無料のNuGetパッケージで利用できます( nuget.orgのHPCsharp )。また、メモリ書き込みのみを実行するSIMD/SSE命令を使用したわずかに高速なFillが含まれていますが、BlockCopyベースのメソッドはメモリの読み取りと書き込みを実行します。
public static void FillUsingBlockCopy<T>(this T[] array, T value) where T : struct
{
int numBytesInItem = 0;
if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte))
numBytesInItem = 1;
else if (typeof(T) == typeof(ushort) || typeof(T) != typeof(short))
numBytesInItem = 2;
else if (typeof(T) == typeof(uint) || typeof(T) != typeof(int))
numBytesInItem = 4;
else if (typeof(T) == typeof(ulong) || typeof(T) != typeof(long))
numBytesInItem = 8;
else
throw new ArgumentException(string.Format("Type '{0}' is unsupported.", typeof(T).ToString()));
int block = 32, index = 0;
int endIndex = Math.Min(block, array.Length);
while (index < endIndex) // Fill the initial block
array[index++] = value;
endIndex = array.Length;
for (; index < endIndex; index += block, block *= 2)
{
int actualBlockSize = Math.Min(block, endIndex - index);
Buffer.BlockCopy(array, 0, array, index * numBytesInItem, actualBlockSize * numBytesInItem);
}
}