Определение числа для эффективности алгоритма


Я уже некоторое время работаю с Java и всегда борюсь за то, чтобы сделать что-то наиболее эффективным способом. К настоящему моменту я в основном пытался сократить количество строк кода, которые у меня есть. Но когда вы начинаете работать с 2d рендерингом, речь идет скорее о том, сколько времени требуется для вычисления определенного фрагмента кода, поскольку он вызывается много раз в секунду.

Мой вопрос:

Есть ли способ измерить, сколько времени требуется для вычисления определенного фрагмента кода в Eclipse, Java,... ?

3 2

3 ответа:

Количество строк очень мало коррелирует со скоростью выполнения программы.

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

Поэтому вместо того, чтобы пытаться "выжать" последний бит производительности / памяти из вашей программы, используя short вместо int, char[] вместо String или любого другого метода, который, по вашему мнению, "оптимизирует" (преждевременная оптимизация) вашу программу, просто сделайте это, используя объекты или типы, которые имеют смысл для вас, поэтому их будет легче поддерживать. Ваш компилятор, интерпретатор, виртуальная машина должны позаботиться об остальном. Если этого не происходит, только тогда вы начинаете искать узкие места и начинаете играть с хак-кодами. Так что же тогда делает программы быстрыми? Алгоритмическая эффективность (по крайней мере, она имеет тенденцию иметь наибольшее значение, если алгоритм / данные структура была спроектирована неправильно). Это то, что изучают ученые-компьютерщики. Предположим, вам даны 2 структуры данных. Массив и односвязный список. Массив хранит вещи в блоке, одну за другой.
+-+-+-+-+-+-+-+
|1|3|2|7|4|6|1|
+-+-+-+-+-+-+-+

Чтобы получить элемент с индексом 3, Вы просто идете к 4-му квадрату и получаете его. Вы знаете, где это, потому что вы знаете, что это 3 после первого квадрата.

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

+-+    +-+    +-+    +-+    +-+    +-+    +-+
|1| -> |3| -> |2| -> |7| -> |4| -> |6| -> |1|
+-+    +-+    +-+    +-+    +-+    +-+    +-+

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

Теперь предположим, что у вас есть массив и SLL, оба содержащие одни и те же данные, с длина n, какой из них будет быстрее? Зависит от того, как вы его используете.

Предположим, вы делаете много вставок в конце списка. Алгоритмы (псевдокод) будут следующими:
Array:
array[list.length] = element to add
increment length field

SLL:
currentNode = first element of SLL

while currentNode has next node:
    currentNode = currentNode's next element

currentNode's next element = new Node(element to add)
increment length field
Как вы можете видеть, в алгоритме массива не имеет значения размер массива. Это всегда требует постоянного количества операций. Предположим, что a[list.length] принимает 1 операцию. Назначение его-это еще одна операция, увеличение поля, а запись его в память - это 2 операции. Это был бы возьмите 4 операции каждый раз. Но если вы посмотрите на алгоритм SLL, то потребуется как минимум list.length количество операций, чтобы найти последний элемент в списке. Другими словами, время, необходимое для добавления элемента в конец SLL, линейно увеличивается по мере увеличения размера SLL t(n) = n, тогда как для массива это больше похоже на t(n) = 4.

Я предлагаю прочитатьбесплатную книгу , написанную моим профессором структур данных. Даже имеет рабочий код на C++ и Java

Во-первых, некоторые придирки. Вы называете этот вопрос ...

Присвоение числа эффективности алгоритма

  • Для алгоритма не существует практической количественной меры "эффективности". Эффективность (как обычно понимается) - это мера "чего-то" по отношению к идеалу / совершенству; например, гипотетическая 100% эффективная паровая машина преобразует всю энергию в сжигаемом угле в полезную "работу". Но для программного обеспечения нет идеала, чтобы мера против. (А если и есть, то мы не можем быть уверены, что это идеал.) Следовательно, "эффективность" - неправильный термин.

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

Итак, как вы оцениваете спектакль?

Что ж, в конечном счете есть только один надежный способ количественной оценки производительности. Вы измеряете его эмпирически. (Как вы это делаете ... и ограничения ... это вопрос, к которому я приду.) Но как насчет теоретических подходов? Общий теоретический подход заключается в анализе алгоритма, чтобы дать вам меру вычислительной сложности . Классическая мера-это большая сложность. Это очень полезная мера, но, к сожалению, большая сложность делает на самом деле не измеряют производительность вообще. Скорее, это способ характеризовать поведение алгоритма по мере увеличения размера задачи. Для иллюстрации рассмотрим следующие алгоритмы сложения чисел B:
    int sum(int[] input) {
        int sum = 0;
        for (int i = 0; i < input.size(); i++) {
            sum += input[i];
        }
        return i;
    }

    int sum(int[] input) {
        int tmp = p(1000);  // calculates the 1000th prime number
        int sum = 0;
        for (int i = 0; i < input.size(); i++) {
            sum += input[i];
        }
        return i;
    }
Мы можем доказать, что обе версии sum имеют сложность O(N), согласно принятым математическим определениям. Но очевидно, что первый будет быстрее второго ... потому что второй делает большой (и бессмысленный) расчет тоже.
Короче говоря: сложность Big-O не является мерой производительности.

Как насчет теоретических показателей эффективности?

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

Так как же вы измеряете производительность?

Наивный ответ заключается в следующем:

  • возьмите измерение часов
  • запустите код
  • возьмите второе измерение часов
  • вычтите первое измерение из второго ... и это ваш ответ.

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

Почему?

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

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

  • Выбор входных данных часто может иметь большое значение.

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

А для таких языков, как Java и C#, есть и другие важные вопросы:

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

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

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

Эти вещи (и, возможно, другие) способствуют тому, что мы называем (в Java) JVM warmup overheads; в частности, компиляция JIT. Если вы не учитываете эти накладные расходы в своей методологии, то ваши результаты могут быть искажены.

Итак, как правильно измерить производительность Java-кода?

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

В наши дни, рекомендуемый способ сделать Java бенчмаркинг-это использовать авторитетный бенчмаркинг фреймворк. Двумя основными кандидатами являютсясуппорт и инструмент Oraclejmh .

И каковы ограничения производительности измерения, о которых вы упомянули?

Я уже упоминал о них выше.
  • Измерения производительности могут быть искажены в зависимости от различных факторов окружающей среды на платформе исполнения.

  • Производительность может зависеть от входных данных ... и это не может быть обнаружено простым измерением.

  • Производительность (например, кода C / C++) может зависеть от компилятора и коммутаторов компилятора.

  • Производительность может зависеть от оборудования; например, скорость процессоров, количество ядер, архитектура памяти и так далее.

Эти факторы могут затруднить составление общих утверждений о производительности конкретного фрагмента кода и проведение общих сравнений между альтернативными версиями одного и того же кода. Как правило, мы можем делать только ограниченные утверждения типа "в системе X, с компилятором Y и входным набором Z показатели производительности равны P, Q, R".

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

Например, если скорость вашего алгоритма равна O (n), то время, которое потребуется для выполнения кода, будет линейным по времени. Если скорость вашего алгоритма равна O(1), то время, которое он займет ваш код для запуска будет постоянным.

Я нашел этот конкретный способ измерения производительности полезным, потому что вы узнаете, что на самом деле это не строки кода, которые будут влиять на скорость, это ваш дизайн кодов, который будет влиять на скорость. Код с более эффективным способом решения проблемы может быть быстрее, чем код с менее эффективным методом с 1/10 строками кода.