Почему структура медленнее чем float? - PullRequest
2 голосов
/ 06 октября 2010

Если у меня есть массив структур MyStruct[]:

struct MyStruct 
{
    float x;
    float y;
}

И это медленнее, чем если бы я делал float [] -> x => i; y => i + 1 (поэтому этот массив в 2 раза больше, чем со структурами).

Разница во времени для 10 000 элементов сравнивает друг друга (два форса внутри): структура 500 мс, массив только с плавающей точкой - 78 мс

Я думал, что структура выглядит как, например. float, int и т. д. (в куче).

Ответы [ 5 ]

2 голосов
/ 06 октября 2010

Во-первых, структуры не обязательно появляются в куче - они могут и часто появляются в стеке.

Что касается ваших измерений производительности, я думаю, вы, должно быть, проверили их неправильно.Используя этот контрольный код, я получаю почти одинаковые результаты производительности для обоих типов:

TwoFloats[] a = new TwoFloats[10000];
float[] b = new float[20000];

void test1()
{
    int count = 0;
    for (int i = 0; i < 10000; i += 1)
    {
        if (a[i].x < 10) count++;
    }
}

void test2()
{
    int count = 0;
    for (int i = 0; i < 20000; i += 2)
    {
        if (b[i] < 10) count++;
    }
}

Результаты:

Method   Iterations per second
test1                 55200000
test2                 54800000
1 голос
/ 06 октября 2010

Вы делаете что-то серьезно неправильно, если у вас есть такие времена.Сравнения с плавающей запятой являются чрезвычайно быстрыми, я синхронизирую их на 2 наносекунд с издержками цикла.Создать такой тест сложно, потому что компилятор JIT оптимизирует содержимое, если вы не используете результат сравнения.

Структура немного быстрее, 1,96 наносекунд против 2,20 наносекунд для float [] намой ноутбук.Так и должно быть, доступ к члену Y структуры не требует дополнительного индекса массива.

Тестовый код:

using System;
using System.Diagnostics;

class Program {
    static void Main(string[] args) {
        var test1 = new float[100000000];  // 100 million
        for (int ix = 0; ix < test1.Length; ++ix) test1[ix] = ix;
        var test2 = new Test[test1.Length / 2];
        for (int ix = 0; ix < test2.Length; ++ix) test2[ix].x = test2[ix].y = ix;
        for (int cnt = 0; cnt < 20; ++cnt) {
            var sw1 = Stopwatch.StartNew();
            bool dummy = false;
            for (int ix = 0; ix < test1.Length; ix += 2) {
                dummy ^= test1[ix] >= test1[ix + 1];
            }
            sw1.Stop();
            var sw2 = Stopwatch.StartNew();
            for (int ix = 0; ix < test2.Length; ++ix) {
                dummy ^= test2[ix].x >= test2[ix].y;
            }
            sw2.Stop();
            Console.Write("", dummy);
            Console.WriteLine("{0} {1}", sw1.ElapsedMilliseconds, sw2.ElapsedMilliseconds);
        }
        Console.ReadLine();
    }
    struct Test {
        public float x;
        public float y;
    }
}
1 голос
/ 06 октября 2010

Я получаю результаты, которые, похоже, согласны с вами (и не согласны с Марком).Мне любопытно, допустил ли я ошибку при построении этого (хотя и грубого) эталона или есть еще один фактор в игре.

Скомпилировано на MS C # для .NET 3.5 framework с VS2008.Режим разблокировки, без отладчика.

Вот мой тестовый код:

class Program {
    static void Main(string[] args) {
        for (int i = 0; i < 10; i++) {
            RunBench();
        }

        Console.ReadKey();
    }

    static void RunBench() {
        Stopwatch sw = new Stopwatch();

        const int numPoints = 10000;
        const int numFloats = numPoints * 2;
        int numEqs = 0;
        float[] rawFloats = new float[numFloats];
        Vec2[] vecs = new Vec2[numPoints];

        Random rnd = new Random();
        for (int i = 0; i < numPoints; i++) {
            rawFloats[i * 2] = (float) rnd.NextDouble();
            rawFloats[i * 2 + 1] = (float)rnd.NextDouble();
            vecs[i] = new Vec2() { X = rawFloats[i * 2], Y = rawFloats[i * 2 + 1] };
        }

        sw.Start();
        for (int i = 0; i < numFloats; i += 2) {
            for (int j = 0; j < numFloats; j += 2) {
                if (i != j &&
                    Math.Abs(rawFloats[i] - rawFloats[j]) < 0.0001 &&
                    Math.Abs(rawFloats[i + 1] - rawFloats[j + 1]) < 0.0001) {
                    numEqs++;
                }
            }
        }
        sw.Stop();

        Console.WriteLine(sw.ElapsedMilliseconds.ToString() + " : numEqs = " + numEqs);

        numEqs = 0;
        sw.Reset();
        sw.Start();
        for (int i = 0; i < numPoints; i++) {
            for (int j = 0; j < numPoints; j++) {
                if (i != j &&
                    Math.Abs(vecs[i].X - vecs[j].X) < 0.0001 &&
                    Math.Abs(vecs[i].Y - vecs[j].Y) < 0.0001) {
                    numEqs++;
                }
            }
        }
        sw.Stop();

        Console.WriteLine(sw.ElapsedMilliseconds.ToString() + " : numEqs = " + numEqs);
    }
}

struct Vec2 {
    public float X;
    public float Y;
}

Редактировать: Ах!Я не повторял правильные суммы.С обновленным кодом мои тайминги выглядят так, как я ожидал:

269 : numEqs = 8
269 : numEqs = 8
270 : numEqs = 2
269 : numEqs = 2
268 : numEqs = 4
270 : numEqs = 4
269 : numEqs = 2
268 : numEqs = 2
270 : numEqs = 6
270 : numEqs = 6
269 : numEqs = 8
268 : numEqs = 8
268 : numEqs = 4
270 : numEqs = 4
269 : numEqs = 6
269 : numEqs = 6
268 : numEqs = 2
270 : numEqs = 2
268 : numEqs = 4
270 : numEqs = 4
0 голосов
/ 06 октября 2010

Код ниже основан на различных способах итерации. На моей машине Test1b занимает почти вдвое больше времени, чем Test1a. Интересно, относится ли это к вашей проблеме.

class Program
{
    struct TwoFloats
    {
        public float x;
        public float y;
    }

    static TwoFloats[] a = new TwoFloats[10000];

    static int Test1a()
    {
        int count = 0;
        for (int i = 0; i < 10000; i += 1)
        {
            if (a[i].x < a[i].y) count++;
        }
        return count;
    }

    static int Test1b()
    {
        int count = 0;
        foreach (TwoFloats t in a)
        {
            if (t.x < t.y) count++;
        }
        return count;
    }

    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int j = 0; j < 5000; ++j)
        {
            Test1a();
        }
        sw.Stop();
        Trace.WriteLine(sw.ElapsedMilliseconds);
        sw.Reset();
        sw.Start();
        for (int j = 0; j < 5000; ++j)
        {
            Test1b();
        }
        sw.Stop();
        Trace.WriteLine(sw.ElapsedMilliseconds);
    }

}
0 голосов
/ 06 октября 2010

Наиболее вероятная причина заключается в том, что оптимизатор времени выполнения C # выполняет лучшую работу, когда вы работаете с плавающими с полными структурами, возможно, потому, что оптимизатор отображает x и y в регистры или аналогично изменениям, не выполненным с полной структурой.

В вашем конкретном примере, кажется, нет какой-либо фундаментальной причины, по которой он не мог бы работать так же хорошо, когда вы используете структуры (трудно убедиться, не увидев фактического кода тестирования), но это не так. Однако было бы интересно сравнить производительность результирующего кода при компиляции с другими реализациями C # (я думаю о моно в Linux).

Я тестировал бенчмарк Рона Уорхолика с моно, и результаты совпадают с оценкой Марка, разница между двумя типами доступа кажется минимальной (версия с плавающими на 1% быстрее). Тем не менее, мне все же следует проводить больше тестов, поскольку не удивительно, что библиотечные вызовы, такие как Math.Abs, занимают много времени, и это может скрыть реальную разницу.

После удаления вызовов Math.Abs ​​и просто выполнения тестов, подобных rawFloats[i] < rawFloats[j], версия структуры становится немного быстрее (около 5%), чем два массива с плавающей точкой.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...