Вычисление и поиск таблиц для синусоидальной производительности?

Используйте флаги BindingFlags.NonPublic и BindingFlags.Instance

FieldInfo[] fields = myType.GetFields(
                         BindingFlags.NonPublic | 
                         BindingFlags.Instance);
18
задан Nosredna 5 September 2009 в 14:16
поделиться

7 ответов

Обновление: дочитать до конца. Похоже, что справочная таблица все-таки быстрее, чем Math.Sin.

Я полагаю, что поисковый подход будет быстрее, чем Math.Sin. Я бы также сказал, что это будет намного быстрее, но ответ Роберта заставил меня подумать, что я все же хочу протестировать это, чтобы быть уверенным. Я много занимаюсь обработкой аудиобуфера и заметил, что такой метод:

for (int i = 0; i < audiodata.Length; i++)
{
    audiodata[i] *= 0.5; 
}

будет выполняться значительно быстрее, чем

for (int i = 0; i < audiodata.Length; i++)
{
    audiodata[i] = Math.Sin(audiodata[i]);
}

. Если разница между Math.Sin и простым умножением значительна, я бы предположил, что разница между Math.Sin и поиском также будет существенной.

Я не знаю, но мой компьютер с Visual Studio находится в подвале, и я слишком устал, чтобы потратить 2 минуты, чтобы определить это.

Обновление : Хорошо, на это ушло более 2 минут (скорее 20), но похоже, что Math.Sin как минимум в два раза быстрее, чем таблица поиска (с использованием словаря). Вот класс, который выполняет Sin с помощью Math.Sin или таблицы поиска:

public class SinBuddy
{
    private Dictionary<double, double> _cachedSins
        = new Dictionary<double, double>();
    private const double _cacheStep = 0.01;
    private double _factor = Math.PI / 180.0;

    public SinBuddy()
    {
        for (double angleDegrees = 0; angleDegrees <= 360.0; 
            angleDegrees += _cacheStep)
        {
            double angleRadians = angleDegrees * _factor;
            _cachedSins.Add(angleDegrees, Math.Sin(angleRadians));
        }
    }

    public double CacheStep
    {
        get
        {
            return _cacheStep;
        }
    }

    public double SinLookup(double angleDegrees)
    {
        double value;
        if (_cachedSins.TryGetValue(angleDegrees, out value))
        {
            return value;
        }
        else
        {
            throw new ArgumentException(
                String.Format("No cached Sin value for {0} degrees",
                angleDegrees));
        }
    }

    public double Sin(double angleDegrees)
    {
        double angleRadians = angleDegrees * _factor;
        return Math.Sin(angleRadians);
    }
}

А вот тестовый / временной код:

SinBuddy buddy = new SinBuddy();

System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
int loops = 200;

// Math.Sin
timer.Start();
for (int i = 0; i < loops; i++)
{
    for (double angleDegrees = 0; angleDegrees <= 360.0; 
        angleDegrees += buddy.CacheStep)
    {
        double d = buddy.Sin(angleDegrees);
    }
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());

// lookup
timer.Start();
for (int i = 0; i < loops; i++)
{
    for (double angleDegrees = 0; angleDegrees <= 360.0;
        angleDegrees += buddy.CacheStep)
    {
        double d = buddy.SinLookup(angleDegrees);
    }
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());

Используя значение шага 0,01 градуса и 200 раз перебирая полный диапазон значений (как в этом code) занимает около 1,4 секунды при использовании Math.Sin и около 3,2 секунды при использовании таблицы поиска словаря. При уменьшении значения шага до 0,001 или 0,0001 поиск по Math.Sin будет еще хуже. Кроме того, этот результат еще больше в пользу использования Math.Sin, поскольку SinBuddy.Sin выполняет умножение, чтобы преобразовать угол в градусах в угол в радианах при каждом вызове, а SinBuddy.SinLookup просто выполняет прямой поиск.

Это дешевый ноутбук (без двухъядерных процессоров и прочего). Роберт, ты мужик! (Но я все еще думаю, что должен получить чек, потому что я выполнил работу.)

Обновление 2 : Хорошо, я идиот ... Оказывается, остановка и перезапуск секундомера не сбрасывает истекшее время миллисекунд, поэтому поиск казался вдвое медленнее, потому что пришло время включить время для вызовов Math.Sin. Кроме того, я перечитал вопрос и понял, что вы говорите о кешировании значений в простом массиве, а не об использовании словаря. Вот мой модифицированный код (я оставляю старый код в качестве предупреждения для будущих поколений):

public class SinBuddy
{
    private Dictionary<double, double> _cachedSins
        = new Dictionary<double, double>();
    private const double _cacheStep = 0.01;
    private double _factor = Math.PI / 180.0;

    private double[] _arrayedSins;

    public SinBuddy()
    {
        // set up dictionary
        for (double angleDegrees = 0; angleDegrees <= 360.0; 
            angleDegrees += _cacheStep)
        {
            double angleRadians = angleDegrees * _factor;
            _cachedSins.Add(angleDegrees, Math.Sin(angleRadians));
        }

        // set up array
        int elements = (int)(360.0 / _cacheStep) + 1;
        _arrayedSins = new double[elements];
        int i = 0;
        for (double angleDegrees = 0; angleDegrees <= 360.0;
            angleDegrees += _cacheStep)
        {
            double angleRadians = angleDegrees * _factor;
            //_cachedSins.Add(angleDegrees, Math.Sin(angleRadians));
            _arrayedSins[i] = Math.Sin(angleRadians);
            i++;
        }
    }

    public double CacheStep
    {
        get
        {
            return _cacheStep;
        }
    }

    public double SinArrayed(double angleDegrees)
    {
        int index = (int)(angleDegrees / _cacheStep);
        return _arrayedSins[index];
    }

    public double SinLookup(double angleDegrees)
    {
        double value;
        if (_cachedSins.TryGetValue(angleDegrees, out value))
        {
            return value;
        }
        else
        {
            throw new ArgumentException(
                String.Format("No cached Sin value for {0} degrees",
                angleDegrees));
        }
    }

    public double Sin(double angleDegrees)
    {
        double angleRadians = angleDegrees * _factor;
        return Math.Sin(angleRadians);
    }
}

И тестовый / временной код:

SinBuddy buddy = new SinBuddy();

System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
int loops = 200;

// Math.Sin
timer.Start();
for (int i = 0; i < loops; i++)
{
    for (double angleDegrees = 0; angleDegrees <= 360.0; 
        angleDegrees += buddy.CacheStep)
    {
        double d = buddy.Sin(angleDegrees);
    }
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());

// lookup
timer = new System.Diagnostics.Stopwatch();
timer.Start();
for (int i = 0; i < loops; i++)
{
    for (double angleDegrees = 0; angleDegrees <= 360.0;
        angleDegrees += buddy.CacheStep)
    {
        double d = buddy.SinLookup(angleDegrees);
    }
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());

// arrayed
timer = new System.Diagnostics.Stopwatch();
timer.Start();
for (int i = 0; i < loops; i++)
{
    for (double angleDegrees = 0; angleDegrees <= 360.0;
        angleDegrees += buddy.CacheStep)
    {
        double d = buddy.SinArrayed(angleDegrees);
    }
}
timer.Stop();
MessageBox.Show(timer.ElapsedMilliseconds.ToString());

Эти результаты совершенно разные. Использование Math.Sin занимает около 850 миллисекунд, таблица поиска словаря занимает около 1300 миллисекунд, а поисковая таблица на основе массива занимает около 600 миллисекунд. Итак, похоже, что (правильно написанная [gulp]) таблица поиска на самом деле немного быстрее, чем использование Math.Sin , но ненамного.

Пожалуйста, проверьте эти результаты самостоятельно, так как я уже продемонстрировал мою некомпетентность.

20
ответ дан 30 November 2019 в 06:39
поделиться

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

Но нет причин пересчитывать одно и то же значение снова и снова.

0
ответ дан 30 November 2019 в 06:39
поделиться

Поскольку вы упоминаете преобразования Фурье как приложение, вы можете также подумать о вычислении синусов / косинусов с помощью уравнений

sin (x + y) = sin (x) cos (y) + cos (x) sin (y)

cos (x + y) = cos (x) cos (y) - sin (x) sin (y)

Т.е. вы можете вычислить sin (n * x), cos (n * x) для n = 0, 1, 2 ... итеративно из sin ((n-1) * x), cos ((n-1) * x) и констант sin (x), cos ( x) с 4 умножениями. Конечно, это работает только в том случае, если вам нужно вычислить sin (x), cos (x) в арифметической последовательности.

Сравнивать подходы без фактической реализации сложно. Это во многом зависит от того, насколько хорошо ваши таблицы помещаются в кеши.

2
ответ дан 30 November 2019 в 06:39
поделиться

Раньше считалось, что поиск по массиву был хорошей оптимизацией для выполнения быстрых триггерных вычислений.

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

15
ответ дан 30 November 2019 в 06:39
поделиться

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

Если вам просто любопытно, вы можете легко написать тест для сравнения скорости. Однако вам нужно помнить, что использование памяти для таблицы поиска может повлиять на разбиение по страницам в более крупных приложениях. Таким образом, даже если разбиение по страницам выполняется быстрее в вашем небольшом тесте, это может замедлить работу в более крупном приложении, которое использует больше памяти.

7
ответ дан 30 November 2019 в 06:39
поделиться

Ответ на этот вопрос полностью зависит от того, сколько значений находится в вашей таблице поиска. Вы говорите, что «домен находится между 0,01 и 360,01», но не говорите, сколько значений в этом диапазоне может быть использовано или насколько точными вам нужны ответы. Простите меня за то, что я не ожидал увидеть значащие цифры, используемые для передачи неявного значения в ненаучном контексте.

Для ответа на этот вопрос все еще требуется дополнительная информация. Каково ожидаемое распределение значений от 0,01 до 360,01? Вы обрабатываете много других данных, кроме простого вычисления sin ()?

36000 значений двойной точности занимают более 256 КБ в памяти; таблица поиска слишком велика, чтобы поместиться в кэш L1 на большинстве машин; если вы проходите прямо через таблицу, вы пропустите L1 один раз за каждый доступ sizeof (cacheline) / sizeof (double), и, вероятно, ударил L2. Если, с другой стороны, доступ к вашей таблице более или менее случайен, вам будет не хватать L1 почти каждый раз, когда вы выполняете поиск.

Это также во многом зависит от математической библиотеки платформы, на которой вы находитесь. Например, распространенные реализации функции sin в i386 составляют от ~ 40 циклов до 400 циклов или даже больше, в зависимости от конкретной микроархитектуры и поставщика библиотеки. Я не рассчитал время для библиотеки Microsoft, поэтому я не знаю точно, где упадет реализация C # Math.sin.

Поскольку загрузка из L2 обычно выполняется быстрее, чем 40 циклов на нормальной платформе, можно разумно ожидать, что таблица поиска Быстрее рассматривать изолированно. Однако я сомневаюсь, что вы вычисляете sin () изолированно; если ваши аргументы для sin () перепрыгивают через всю таблицу, вы будете удалять из кэша другие данные, необходимые для других этапов вычислений; хотя вычисление sin () становится быстрее, замедление других частей ваших вычислений может более чем перевесить ускорение. Только тщательное измерение может действительно ответить на этот вопрос.

Могу ли я понять из ваших других комментариев, что вы делаете это как часть вычисления БПФ? Есть ли причина, по которой вам нужно использовать собственное БПФ вместо использования одной из многочисленных уже существующих реализаций исключительно высокого качества?

Вы делаете это как часть вычисления БПФ? Есть ли причина, по которой вам нужно использовать собственное БПФ вместо использования одной из многочисленных уже существующих реализаций чрезвычайно высокого качества?

Вы делаете это как часть вычисления БПФ? Есть ли причина, по которой вам нужно использовать собственное БПФ вместо использования одной из многочисленных уже существующих реализаций исключительно высокого качества?

2
ответ дан 30 November 2019 в 06:39
поделиться

Math.Sin быстрее. Люди, которые писали, умны и используют поиск в таблицах, когда они точны и быстрее, и используют математику, когда это быстрее. И в этом домене нет ничего, что могло бы сделать его особенно быстрым, первое, что делают большинство реализаций триггерных функций, так или иначе сопоставляются с подходящим доменом.

1
ответ дан 30 November 2019 в 06:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: