Re[5]: вдогонку
От: Sinclair Россия https://github.com/evilguest/
Дата: 30.10.08 12:20
Оценка: 6 (1) +5
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Полностью абстрагируясь от эффективного вычисления процентов , отмечу лишь, что сделать код с помощью каких-то высокоуровненвых конструкций проще — можно, понятнее — тоже можно, а вот эффективнее — дудки. Потому что предельно эффективная программа — это наилучшим образом оптимизированная программа, написанная на ассемблере. А в этом самом ассемблере есть только презренные присваивания, if, с грехом пополам циклы, и ни одной лямбды
Как и следовало ожидать, провокационная тема ушла в глубокий флейм, перемежаемый пенисометрией.
Поскольку никто из нормальных людей в такие глубины веток не ныряет, напишу правильный ответ здесь.

Итак, не отвлекаясь на фигню, отвечаем на вопрос: "можно ли сделать код эффективнее с помошью каких-то высокоуровневых конструкций?".
Во-первых, нужно отметить, что вопрос про то, какой код исполняется эффективнее — ассемблерный или еще какой — не стоит. Как ты верно подметил в глубинах флейма, "процессор всё равно не умеет исполнять никакого другого кода, кроме ассемблерного".
Отлично, тогда что же мы сравниваем и с чем? Вот у нас одна программа (очевидно, ассемблерная), и вот другая программа. Очевидно, тоже ассемблерная. Одна из них может оказаться эффективнее другой. Какая же программа будет именно такой?
Для начала, нужно понять, что такое "более эффективная". Это не такой тривиальный вопрос, как кажется. Дело даже не столько в том, что количество тактов, потребляемое некоторой программой для решения некоторой задачи, зависит от массы сторонних факторов (например, насколько часто процессору приходится отвлекаться от нее на обслуживание чего-то другого), а в том, что сначала нужно определиться с тем, что такое "решение задачи".

В стародавние времена программы (и задачи) были одноразовыми. Нужно вам, скажем, посчитать число Пи до тысячного знака. Ок, пишем программу, которая считает число Пи; запускаем, дожидаемся результата — ок, задача решена. Очевидно, что второй раз нам считать число Пи уже не надо. (Если, конечно, мы не забыли вывести куда-то результат вычислений ). Опять же очевидно, что при многократном запуске расчета числа Пи эффективнее всех прочих будет программа, которая устроена примерно так (в терминах C++):
сout << "3.1415926...";

(Производители процессоров об этом в курсе; поэтому инструкция fldpi выполняется значительно быстрее, чем самый оптимизированный расчет суммы несложного ряда)

Cейчас мы подразумеваем под "задачей" семейство задач в том древнем смысле. То есть когда мы говорим "нужна программа для сортировки массива" мы подразумеваем, что одна и та же программа будет использоваться для сортировки различных массивов. А вовсе не для сортировки конкретного массива.

В чем сложность? В том, что раньше мы могли рассуждать об эффективности в терминах расходов памяти и тактов процессора, нужных для решения конкретной задачи. Нужно тебе X знаков после запятой в разложении Пи? Ок, это будет стоить N(X) тактов и потребует P(X) байт памяти.

Теперь так рассуждать нельзя — затраты в тактах и байтах существенным образом зависят от того, какие именно данные мы подали на вход. На пальцах: пузырек, запущенный на отсортированном массиве длины N выполнит N сравнений, 0 обменов и успокоится. На обратно отсортированных данных он выполнит N^2/2 сравнений и обменов.

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

На данном этапе мы уже поняли, что если мы беремся сравнивать две программы по эффективности, то нам нужно задатсья некоторым распределением входных данных. Если мы этого не сделаем, то сравнение не имеет смысла — в одних условиях выиграет программа А, в других — программа B.

Итак, критерий сравнения эффективности программ — это матожидание затрат памяти и тактов на решение одной из задач, заданных некоторым распределением.

Поговорим об идеальной эффективности.

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

В реальном мире такие оценки очень редко можно получить заранее, до запуска программы.

Как же нам быть в таком случае? Выбирать программу случайно?
Способ, очевидно, есть. Вместо того, чтобы подготовить одну программу, мы можем подготовить семейство программ, которые имеют одну и ту же семантику (например, все они сортируют любой массив), но отличаются характеристиками производительности для различных входных данных.
И только тогда, когда мы узнаем, какие же данные поступают к нам на вход, мы выбираем ту из программ, которая лучше подходит для конкретного случая.

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

Осталось понять, можно ли применить этот подход, оставаясь в рамках "наилучшим образом оптимизированной программы, написанной на ассемблере", и "можно ли сделать код эффективнее с помошью каких-то высокоуровневых конструкций". Об этом я напишу завтра, потому что сегодня пора домой
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.