基准不一致的结果

时间:2012-12-01 20:42:18

标签: c# .net benchmarking

我试图分析一个功能。该函数用于将结构转换为数组。我有两种不同的方法,使用编组或BitConverter。当然,编组方法使得单个函数可以在给定某些条件下几乎适用于所有结构。 BitConverter需要每个结构的自定义函数。我最初的想法是BitConverter会更快但我的测试结果不一致。

以下是基准测试的剪切和粘贴。

我已经尝试过多种不同形式的基准测试。 当我首先对BitConverter函数进行基准测试时,它往往会更快。 当我首先对Marshaling函数进行基准测试时,它往往会更快。 我错过了什么?

摘要显示流程。这不是基准流程的实际代码。

main()
{
    Stopwatch watch = new Stopwatch;
    // To take care of JIT
    bitConverterFunction();
    marshalingFunction();

    //Thread.Sleep(0);   // I've tried this thinking it had to do with context switching issues but the results were basically the same.
    watch.Start();
    for(i=0; i<iterations; i++)
    {
         bitConverterFunction();
    }
    watch.Stop();

    Timespan bitConverterTime = watch.Elapsed;
    //Thread.Sleep(0);   // I've tried this thinking it had to do with context switching issues
    watch.Restart();
    for(i=0; i<iterations; i++)
    {
         marshalingFunction();
    }
    watch.Stop();
    Timespan marshalingTime = watch.Elapsed;


    // it seems that whichever function is run first, tends to be the quickest.

}

如果你想测试真实代码

using System;
using BenchmarkTool;

namespace BenchmarkConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            Benchmarks.StructToArrayConversion(100);
            Benchmarks.StructToArrayConversion(1000);
            Benchmarks.StructToArrayConversion(10000);
            Benchmarks.StructToArrayConversion(100000);

            Console.WriteLine("Press any key to continue.");
            Console.ReadKey();
        }
    }
}


using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using NUnit.Framework;

namespace BenchmarkTool
{
    [TestFixture]
    public static class Benchmarks
    {
        [TestCase(100)]
        [TestCase(1000)]
        [TestCase(10000)]
        [TestCase(100000)]
        [TestCase(1000000)]
        public static void StructToArrayConversion(int iteration = 100)
        {
            Stopwatch watch = new Stopwatch();
            EntityStatePDU6 state = new EntityStatePDU6()
                {
                    Version = 0,
                    ExerciseID = 0x01,
                    PDUType = 0x02,
                    Family = 0x03,
                    Timestamp = 0x07060504,
                    Length = 0x0908,
                    Site = 0x0D0C,
                    Application = 0X0F0E,
                    Entity = 0X1110,
                    NumArticulationParams = 0X13,
                    VelocityX = BitConverter.ToSingle(new byte[] {0x14, 0x15, 0x16, 0x17}, 0),
                    VelocityY = BitConverter.ToSingle(new byte[] {0x18, 0x19, 0x1A, 0x1B}, 0),
                    VelocityZ = BitConverter.ToSingle(new byte[] {0x1C, 0x1D, 0x1E, 0x1F}, 0),
                    LocationX = BitConverter.ToSingle(new byte[] {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}, 0),
                    LocationY = BitConverter.ToSingle(new byte[] {0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F}, 0),
                    LocationZ = BitConverter.ToSingle(new byte[] {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37}, 0),
                    Roll = BitConverter.ToSingle(new byte[] {0x38, 0x39, 0x3A, 0x3B}, 0),
                    Pitch = BitConverter.ToSingle(new byte[] {0x3C, 0x3D, 0x3E, 0x3F}, 0),
                    Heading = BitConverter.ToSingle(new byte[] {0x40, 0x41, 0x42, 0x43}, 0),
                    Appearance = 0X47464544
                };

            // To take care of JIT
            ToArrayBitConverter(state);
            state.ToByteArray();

            Console.WriteLine("*** Benchmark Start ***");
            Console.WriteLine("BitConverter Benchmark");
            byte[] bitconverterArray = ToArrayBitConverter(state);
            //Thread.Sleep(0);
            watch.Start();
            for(int i = 0; i < iteration; i++)
            {
                bitconverterArray = ToArrayBitConverter(state);
            }
            watch.Stop();
            TimeSpan bitConverterTime = watch.Elapsed;

            Console.WriteLine("{0} Iterations: {1}", iteration, watch.Elapsed.TotalSeconds.ToString("0.0000000"));
            Console.WriteLine();

            Console.WriteLine("Marshal StructToPtr Benchmark");
            byte[] marshalArray = null;
            //Thread.Sleep(0);
            watch.Restart();
            for (int i = 0; i < iteration; i++)
            {
                marshalArray = state.ToByteArray();
            }
            watch.Stop();
            TimeSpan marshalTime = watch.Elapsed;

            Console.WriteLine("{0} Iterations: {1}", iteration, watch.Elapsed.TotalSeconds.ToString("0.0000000"));

            Console.WriteLine();
            Console.WriteLine("Results");
            Console.WriteLine("{0} Faster", marshalTime < bitConverterTime ? "Marshaling" : "BitConverter");
            Console.WriteLine("Speed Ratio: {0}", marshalTime < bitConverterTime ? bitConverterTime.TotalSeconds / marshalTime.TotalSeconds : marshalTime.TotalSeconds / bitConverterTime.TotalSeconds);

            Console.WriteLine("**********************************");
            Console.WriteLine();
            Assert.AreEqual(bitconverterArray.Length, marshalArray.Length);
            for(int i = 0; i < bitconverterArray.Length; i++)
            {
                Assert.AreEqual(marshalArray[i],bitconverterArray[i], "@index " + i);
            }
        }

        public static byte[] ToArrayBitConverter(EntityStatePDU6 entity)
        {
            int size = Marshal.SizeOf(typeof (EntityStatePDU6));
            byte[] array = new byte[size];
            array[0] = entity.Version;
            array[1] = entity.ExerciseID;
            array[2] = entity.PDUType;
            array[3] = entity.Family;
            array[4] = (byte)((0xFF & entity.Timestamp));
            array[5] = (byte)((0xFF00 & entity.Timestamp) >> 8);
            array[6] = (byte)((0xFF0000 & entity.Timestamp) >> 16);
            array[7] = (byte)((0xFF000000 & entity.Timestamp) >> 24);
            array[8] = (byte)((0xFF & entity.Length));
            array[9] = (byte)((0xFF00 & entity.Length) >> 8);
            // Padding1: array[10], array[11]
            array[12] = (byte)((0xFF & entity.Site));
            array[13] = (byte)((0xFF00 & entity.Site) >> 8);
            array[14] = (byte)((0xFF & entity.Application));
            array[15] = (byte)((0xFF00 & entity.Application) >> 8);
            array[16] = (byte)((0xFF & entity.Entity));
            array[17] = (byte)((0xFF00 & entity.Entity) >> 8);
            //padding2 array[18]
            array[19] = entity.NumArticulationParams;

            byte[] bytes = BitConverter.GetBytes(entity.VelocityX);
            array[20] = bytes[0];
            array[21] = bytes[1];
            array[22] = bytes[2];
            array[23] = bytes[3];
            bytes = BitConverter.GetBytes(entity.VelocityY);
            array[24] = bytes[0];
            array[25] = bytes[1];
            array[26] = bytes[2];
            array[27] = bytes[3];
            bytes = BitConverter.GetBytes(entity.VelocityZ);
            array[28] = bytes[0];
            array[29] = bytes[1];
            array[30] = bytes[2];
            array[31] = bytes[3];

            bytes = BitConverter.GetBytes(entity.LocationX);
            array[32] = bytes[0];
            array[33] = bytes[1];
            array[34] = bytes[2];
            array[35] = bytes[3];
            array[36] = bytes[4];
            array[37] = bytes[5];
            array[38] = bytes[6];
            array[39] = bytes[7];

            bytes = BitConverter.GetBytes(entity.LocationY);
            array[40] = bytes[0];
            array[41] = bytes[1];
            array[42] = bytes[2];
            array[43] = bytes[3];
            array[44] = bytes[4];
            array[45] = bytes[5];
            array[46] = bytes[6];
            array[47] = bytes[7];

            bytes = BitConverter.GetBytes(entity.LocationZ);
            array[48] = bytes[0];
            array[49] = bytes[1];
            array[50] = bytes[2];
            array[51] = bytes[3];
            array[52] = bytes[4];
            array[53] = bytes[5];
            array[54] = bytes[6];
            array[55] = bytes[7];

            bytes = BitConverter.GetBytes(entity.Roll);
            array[56] = bytes[0];
            array[57] = bytes[1];
            array[58] = bytes[2];
            array[59] = bytes[3];
            bytes = BitConverter.GetBytes(entity.Pitch);
            array[60] = bytes[0];
            array[61] = bytes[1];
            array[62] = bytes[2];
            array[63] = bytes[3];
            bytes = BitConverter.GetBytes(entity.Heading);
            array[64] = bytes[0];
            array[65] = bytes[1];
            array[66] = bytes[2];
            array[67] = bytes[3];

            array[68] = (byte)((0xFF & entity.Appearance));
            array[69] = (byte)((0xFF00 & entity.Appearance) >> 8);
            array[70] = (byte)((0xFF0000 & entity.Appearance) >> 16);
            array[71] = (byte)((0xFF000000 & entity.Appearance) >> 24);

            return array;
        }

        public static Byte[] ToByteArray<T>(this T obj) where T : struct
        {
            int size = Marshal.SizeOf(obj);
            var arr = new byte[size];
            IntPtr ptr = Marshal.AllocHGlobal(size);

            Marshal.StructureToPtr(obj, ptr, false);
            Marshal.Copy(ptr, arr, 0, size);
            Marshal.FreeHGlobal(ptr);

            return arr;
        }
    }

    public struct EntityStatePDU6
    {
        // PDU Header 12 Bytes
        public byte Version;
        public byte ExerciseID;
        public byte PDUType;
        public byte Family;
        public uint Timestamp;
        public ushort Length;
        public ushort Padding1;

        // Entity ID 6 bytes
        public ushort Site;
        public ushort Application;
        public ushort Entity;

        public byte Padding2;

        public byte NumArticulationParams;

        public float VelocityX;
        public float VelocityY;
        public float VelocityZ;

        public double LocationX;
        public double LocationY;
        public double LocationZ;

        public float Roll;
        public float Pitch;
        public float Heading;

        public uint Appearance;


    }


}

1 个答案:

答案 0 :(得分:1)

100000以下的任何情况都太小而无法获得一致的结果。

即使在相同代码的运行之间(> 2x时序差异),结果也非常不一致。这让我觉得生成了大量的垃圾,结果主要是垃圾收集开始和垃圾收集器的性能。

我在停止秒表后添加了一些GC.Collect次调用,这使得结果更加一致(运行之间的差异为+/- 10%)。对于100000和1000000次迭代,Marshaling的速度通常为1.5-2倍。这是针对Release | x86编译的Mono 2.10.8.1,因此您的里程可能会有所不同。