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

Можно на самом деле выпустить Объект приложения Excel чисто, но действительно необходимо заботиться.

совет поддержать именованную ссылку для абсолютно каждого COM-объекта Вы получаете доступ и затем явно выпускаете, это через Marshal.FinalReleaseComObject() корректно в теории, но, к сожалению, очень трудно справиться на практике. Если Вы когда-нибудь будете скользить где-нибудь и использовать "две точки" или выполнять итерации ячеек через for each цикл или какой-либо другой подобный вид команды, то Вы будете иметь не имеющие ссылки COM-объекты и рискнете подвешиванием. В этом случае не было бы никакого способа найти причину в коде; необходимо было бы рассмотреть весь код глазом и надо надеяться найти причину, задача, которая могла быть почти невозможна для крупного проекта.

хорошие новости - то, что Вы не должны на самом деле поддерживать именованную ссылку на переменную к каждому COM-объекту, который Вы используете. Вместо этого звоните GC.Collect(), и затем GC.WaitForPendingFinalizers() для выпуска весь (обычно незначительный) возражает, к которому Вы не держите ссылку, и затем явно выпускаете объекты, к которым Вы действительно держите именованную ссылку на переменную.

необходимо также выпустить именованные ссылки, в обратном порядке важные: расположитесь возражает сначала, затем рабочие листы, рабочие книги, и затем наконец Ваш Объект приложения Excel.

, Например, предполагая, что у Вас была переменная объекта Диапазона, названная xlRng, Переменная документа, названная xlSheet, переменная Рабочей книги, названная xlBook и переменная Excel Application, названная xlApp, тогда Ваш код очистки мог посмотреть что-то как следующее:

// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();

Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);

xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);

xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);

В большинстве примеров кода Вы будете видеть чистку COM-объектов от.NET, GC.Collect() и GC.WaitForPendingFinalizers(), вызовы выполняются ДВАЖДЫ как в:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();

Это не должно требоваться, однако, если Вы не используете Инструменты Visual Studio для Office (VSTO), который использует финализаторы, которые заставляют весь график объектов быть продвинутым в очереди завершения. Такие объекты не были бы выпущены до следующий сборка "мусора". Однако, если Вы не используете VSTO, необходимо быть в состоянии звонить GC.Collect() и GC.WaitForPendingFinalizers() только однажды.

я знаю, что явно вызов GC.Collect() нет - нет (и конечно выполнение его дважды звучит очень болезненным), но нет никакого пути вокруг этого, чтобы быть честным. Посредством нормального функционирования Вы генерируете скрытые объекты, на которые Вы не держите ссылки, которую Вы, поэтому, не можете выпустить ни через какие другие средства кроме вызова GC.Collect().

Это - сложная тема, но это действительно - все, которое существует к ней. Как только Вы устанавливаете этот шаблон для своей процедуры очистки, которую можно обычно кодировать, без потребности в обертках, и т.д.:-)

, у меня есть учебное руководство на этом здесь:

Программы Office Автоматизации с VB.Net / COM Interop

Это записано для VB.NET, но не пугайтесь этого, принципы являются точно тем же как тогда, когда с помощью C#.

29
задан dlamblin 18 July 2009 в 06:40
поделиться

6 ответов

Это также зависит от сочетания инструкций. У вашего процессора всегда будет несколько вычислительных блоков, и вы получите максимальную пропускную способность, если все они будут заполнены все время. Таким образом, выполнение цикла mul так же быстро, как выполнение цикла или добавления - но то же самое не выполняется, если выражение становится более сложным.

Например, возьмите этот цикл:

for(int j=0;j<NUMITER;j++) {
  for(int i=1;i<NUMEL;i++) {
    bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
  }
}

для NUMITER = 10 ^ 7, NUMEL = 10 ^ 2, оба массива инициализированы небольшими положительными числами (NaN намного медленнее), это занимает 6,0 секунд с использованием удвоений в 64-битной процедуре. Если я заменю цикл на

bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;

, это займет всего 1,7 секунды ... так что, поскольку мы «перестарались» с добавлениями, muls были по существу бесплатными; и сокращение добавок помогло. Еще больше сбивает с толку:

bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;

- тот же дистрибутив mul / add, но теперь константа добавляется, а не умножается - занимает 3,7 секунды. Ваш процессор, вероятно, оптимизирован для более эффективного выполнения типичных численных вычислений; поэтому точечный продукт, такой как суммы множителей и масштабированные суммы, примерно так хорош, как и получается; добавление констант встречается не так часто, так что это медленнее ...

bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/

снова занимает 1,7 секунды.

bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/

(то же, что и начальный цикл, но без дорогостоящего добавления констант: 2,1 секунды)

bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/

(в основном множители, но один сложение: 1,9 секунды)

Итак, в основном; Трудно сказать, что быстрее, но если вы хотите избежать узких мест, более важно иметь разумное сочетание, избегать NaN или INF, избегать добавления констант. Что бы вы ни делали, убедитесь, что вы тестируете и тестируете различные настройки компилятора, поскольку часто небольшие изменения могут просто иметь значение.

Еще несколько случаев:

bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86
23
ответ дан 28 November 2019 в 01:36
поделиться

Теоретически информация находится здесь:

Справочное руководство по оптимизации архитектур Intel®64 и IA-32, ПРИЛОЖЕНИЕ C ЗАДЕРЖКА ИНСТРУКЦИИ И ПРОИЗВОДИТЕЛЬНОСТЬ

Для каждого процессора, который они перечисляют, задержка в FMUL очень близка к задержке в FADD или FDIV. На некоторых старых процессорах FDIV в 2–3 раза медленнее, чем на более новых процессорах, то же самое, что и FMUL.

Предостережения:

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

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

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

19
ответ дан 28 November 2019 в 01:36
поделиться

The best way to answer this question is to actually write a benchmark/profile of the processing you need to do. Empirical should be used over theoretical when ever possible. Especially when it easy to attain.

If you already know different implementations of the Math you need to do, you could write a a few different code transfermations of the math and see where your performance peaks. This will allow the processor/compiler to generate different execution streams to fill the processor pipelines and give you a concrete answer to your answer.

If you are interest in specifically the performance of DIV/MUL/ADD/SUB type instructions you could even toss in some inline assembly to control specifically which variants of these instruction are executed. However you need to make sure you're keeping multilple execution units busy to get a good idea of the performance the system is capable of.

Also doing something like this would allow you to compare performance on multiple variations of the processor by simply running the same program on them, and could also allow you to factor in the motherboard differences.

Edit:

Basic architecture of a +- is identical. So they logically take the same time to compute. * on the other hand, require multiple layers, typically constructed out of "full adders" to complete a single operation. This garentees that while a * can be issued to the pipeline every cycle it will have a higher latency than an add/subtract circuit. A fp / operation is typically implemented using an approximation method which iteratively converges towards the correct answer over time. These types of approximations are typically implemented via multiplication. So for floating point you can generally assume division will take longer because it's impractical to "unroll" the multiplications (which is already a large circuit in and of it's self) into pipeline of a multitude of multiplier circuits. Still the performance of a given system is best measured via testing.

7
ответ дан 28 November 2019 в 01:36
поделиться

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

1
ответ дан 28 November 2019 в 01:36
поделиться

Разница в скорости * / vs + - зависит от архитектуры вашего процессора. В целом и с x86 в частности на современных процессорах разница в скорости стала меньше. * должно быть близко к +, если есть сомнения: просто экспериментируйте. Если у вас действительно сложная проблема с большим количеством операций FP, также рассмотрите возможность использования вашего графического процессора (GeForce, ...), который работает как векторный процессор.

1
ответ дан 28 November 2019 в 01:36
поделиться

Вероятно, разница во времени между умножением и сложением очень мала. с другой стороны, деление по-прежнему значительно медленнее умножения из-за его рекурсивной природы. в современной архитектуре x86 инструкции sse следует учитывать при выполнении операций с плавающей запятой, а не при использовании fpu. Хотя хороший компилятор C / C ++ должен дать вам возможность использовать sse вместо fpu.

-1
ответ дан 28 November 2019 в 01:36
поделиться
Другие вопросы по тегам:

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