Category: компьютеры

Category was added automatically. Read all entries about "компьютеры".

Сложно ли упереться в скорость последовательной работы с памятью?

Я показывал простой способ, которым можно сравнить скорость последовательного и случайного доступа к памяти, запуская идентичный код с разными параметрами. Получалось отличие почти в 19 раз. Случайный доступ к памяти работает так медленно, что даже сложный код, работающий с память непоследовательно, скорей всего будет ограничен в скорости своей работы именно памятью. Однако, последовательный доступ давал скорость работы с памятью не превышающую 3 ГБ в секунду. А это далеко до предела. Код упирался в CPU, а не в скорость работы с памятью. Очевидно, что упростив код можно увеличить скорость его работы. Проведем эксперимент.

Collapse )

Память: последовательный доступ vs случайный доступ. Как замерить?

Есть множество книг, инструментов и заметок про скорость работы памяти. Общеизвестен факт о том, что случайный доступ в оперативную память работает существенно дольше последовательного доступа (то же относится и к жесткому диску). Но как эти знания и замеры применить к конкретной задаче и платформе? Вот недавно пролетала замечательная ссылка Latency Numbers Every Programmer Should Know, где написано, что "L1 Cache Reference" это 0.5 наносекунд, а "Main memory reference" это 100 нс, то есть в 200 раз медленней. Так ли это на самом деле? Как это соотнести с реальным кодом на языке высокого уровня?

Collapse )

Процессорно-специфичная оптимизация кода

Когда я программировал в начале 1990-х годов, вполне нормальным делом был следующий подход к оптимизации кода. Сначала код программы писался на языке высокого уровня, потом в программе выявлялось узкое место и, после исчерпания алгоритмических оптимизаций, самое узкое место переписывалось на ассемблере. Порой, переписывание только внутреннего цикла на ассемблере позволяло увеличить производительность в разы! Я помню момент, когда я это делал последний раз. Я был счастливым обладателем нового процессора Pentium и, написав весьма эффективный алгоритм, остался недоволен его производительностью. Я сел и переписал самый главный цикла алгоритма на встроенном ассемблере. После чего снова произвел измерения и, к своему ужасу, увидел что производительность кода уменьшилась. Новый процессор имел два конвейера и сложные правила, которые определяли условия, при которых возможно выполнение двух операций за такт. Сложные для человека, но подъемные для компилятора. Компилятор не хуже меня справился с задачей распределения регистров и переставил набор ассемблерных команд так, чтобы уменьшить количество необходимых для выполнения кода тактов процессора. Я мог бы научиться это делать и сам, и, посвятив себя этому, наверняка научился бы это делать лучше компилятора, но в тот момент я решил, что есть более интересные области на изучение которых я могу потратить свое время.

Collapse )

Узкое место или немного о SIMD

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

Collapse )

Память, пропускная способность и многопоточность

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

Collapse )

Транзакционная память от Intel для x86

Мы стоим на пороге новый эры в многопоточном программировании. Компания Intel анонсировала примитивы для транзакционной синхронизации в будущей микроархитектуре Haswell выход которой в свет планируется в 2013 году. Есть все шансы, что он станет первым процессором с подобным механизмом, который доступен широкой публике. Там будет реализована поддержка аппаратного устранения блокировки с помощью префиксов XACQUIRE/XRELEASE и аппаратные транзакции в памяти с помощью инструкций XBEGIN/XEND/XABORT. Оба этих подхода становятся возможными благодаря тому, что в процессоре появится механизм, который позволит ему поддерживать множество прочитанных и множество измененных кэш-линий, отслеживать возникающие конфликты (два доступа из разных транзакций, один из которых на запись), и, что важно, сохранять все изменения в рамках транзакции в основную память атомарно с точки зрения других процессоров или все их отменять.

Collapse )

Расположение данных в памяти или знай что замеряешь 2

В предыдущей заметке я привел парадоксальные результаты замера скорости итерации по ArrayList<Integer>: итерация по первым 1M элементам работает медленней на моем тесте (4.62 нс на элемент), чем итерация по 10M элементам этого же массива (3.67 нс на элемент). Более того, наблюдаемый эффект очень не стабилен. Он сильно зависит от используемой версии JVM и от её настроек.

Сразу скажу, что этот эффект ни как не связан с JIT компиляцией Java кода. При замерах времени в своей тестовой программе я не учитываю первый проход, во время которого основной цикл компилируется, и вообще использую Java HotSpot Server VM, которая создает машинный код, оптимизированный на том же уровне, как это делают популярные C/C++ компиляторы.

Collapse )

Оптимизация важна или ByteBuffer vs int[]

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

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

Collapse )

Второй день JavaOne

Слушал много сессий :) Neal Gafter рассказывал про Closures, Alex Buckley про модульность, Cliff Click cделал очень веселый рассказ про data races.

К сожалению, Cliff Click подтвердил что "state of the art and state of the practiсe" в отладке data races это визуальные инспекции кода, аккуратное документирования и т.п. В общем, читайте замечательную книгу "Concurrency in Practice" by Brian Goetz. Процесс абсолютно не масштабируемый, но ничего лучше сейчас просто нет. Единственный инструмент каторый как-то может помочь на практите это FindBugs. Его автор "Mr. Skip-List" Bill Pugh, как всегда, сделал шикарный доклад с примерам про то как казалось бы абсолютно идиотские ошибки (типа метода equals который всегда возвращает false), после добавления их описания в FindBugs, были обнаружены в десятке мест в JDK, Glassfish, TomCat и других больших проектах.

Слушал доклад про Transactional Memory. В то время как существуем масса реализаций STM (software transactional memory), они все создают существенные накладные рассходы. Однако есть и перспективное направление развития -- Hardware Transactional Memory. Именно здесь нас скорей всего ждет прорыв в направлении масштабируемого многопоточного программирования. Но сначала должны появиться массовые процессоры поддерживающие транзакции с памятью на аппаратном уровне.