Что означает quad kill
Термины и сленг комментаторов матчей CS:GO
Контера, Контры, Си Ти (CT – Counter-Terrorist), КэТэ (КТ), спецназ – игроки, которые противостоят террористам – «сторона защиты». Победа в раунде контерам зачисляется в таких случаях:
Карта (map) – виртуальный объект в игре, в пределах которого происходит сражение. В данное время на турнирах CS:GO такой набор карт (Маппул, map pool):
Пики, пикать (pick) – термин применяется в нескольких ситуациях:
Тиммейт (Teammate – напарник) – коллега по команде.
Тимкилл (Team Kill) – убийство коллеги по команде.
Скилл (Skill) – уровень (умение) игры.
Личный скилл (Individual skill) – натренированные индивидуальные технические, тактические и стратегические навыки игры (стрельба, использование гранат, выбор позиции, реакция и т.д.).
Тимплэй (Teamplay) – сыгранность команды в целом. Один из ключевых факторов для победы.
Дизмораль – падение общего психологического настроя команды, что приводит к снижению уровня игры. Часто возникает, когда в сложных ситуациях один или несколько игроков начинают нервничать и кричать на членов команды.
Оружие
Калаш, Ака – автомат AK-47 (Террористы).
Эмка, Кольт – штурмовая винтовка M4A4 или M4A1-S.
Хае, Хаешка, Осколка, Гренка, Грена (hegrenade) – осколочная наступательная граната.
Флэшка/Флэха/Флэша, Световуха, Слеповуха/Слепа, Световая, Flashbang – световая ослепляющая граната.
Дымовуха, Дым, Смоук (Smoke) – дымовая граната (может загасить молик).
Засмочить — задымить часть территории дымовыми гранатами.
Броня, Бронник, Бронь, Армор – бронежилет для защиты
Кастрюля – защитный шлем.
Фраг, Килл, Минус (Frag, Kill) – убитый противник.
Фраг-лидер – игрок, который регулярно показывает самый высокий уровень убийств в матчах (с психологической точки зрения очень важно, чтобы фраг-лидер не стал обычным фрагером, который играет только ради фрагов).
Хэдшoт, Хэд, Headshot – точное попадание в голову.
Без головы – попадание в голову без убийства.
Красный — игрок, с уровнем здоровья менее 20%.
Флешить/зафлешить – ослеплять/ослепить световой гранатой.
Флешёный, белый – игрок, который ослеплен световой гранатой.
На шифте/шифтить — движение игрока с зажатой клавишей Shift – медленно, но бесшумно.
Раздамажить (damage – повреждение, урон) – нанесение серьезного вреда здоровью соперника/ов, но еще не убийство.
Энтри-килл, Энтри-Фраг (Entry frag / Openfrag) – первое убийство в раунде, считается важным событием для команды, которая его сделала.
Зажим/Спрей (Spray) – стрельба сплошной очередью (зажав левую клавишу мыши).
Флик-шот – точный выстрел после резкого перевода прицела на цель без предварительного прицеливания. Например, снайпер в прицел контролирует зону, в которую может выйти противник слева или справа. При этом прицел держит посередине от возможных точек выхода. После появления соперника игрок резко бросает прицел в нужную сторону и точно стреляет.
Прострел (Wall-banging) – стрельба через непрозрачные предметы (дверь, труба, ящик, стена и т.п.) по игроку, который может прятаться за этими предметами.
Стрэйф (Strafe) – игровая механика передвижения по не прямой траектории для затруднения попадания противником. Это может быть, как боковое перемещение влево-вправо, так и сложное перемещение зигзагами с одновременной стрельбой.
Оптика/Зум (Zoom) – оптический прицел.
Ноускоп/Ноузум (No-scope/No-zoom) – выстрел из снайперской винтовки, не используя оптический прицел.
Фастзум (FastZoom) – техника стрельбы из снайперской винтовки (в большинстве случаев с близкого расстояния). Противник высматривается без зума, как только он появляется, включается зум и производится моментальный выстрел.
Джампшот (Jumpshot) — стрельба в прыжке;
Мансовать, мансует – действия игрока/игроков с целью запутать или обмануть противника. Само слово (глагол) происходит от одесского местного фразеологизма МАНСЫ (россказни, лапша на уши, сомнительные поступки и слухи).
Буст, Подсадка (Boost) – способ забраться в труднодоступное высокое место, залезая на присевшего игрока.
Ретейк (ReTake) — попытка вернуть утраченный контроль над определенной точкой. Чаще все применяется к точкам установки бомбы.
Допы – дополнительные раунды на карте. При счете в раунде 15:15 команды играют еще 6 раундов, для победы нужно выиграть 4 раунда. Если команды сыграют 3-3, то играют еще 6 раундов. Допы назначаются, пока одна из команд не выиграет 4 раунда.
Мап-поинт (Map Point) – так называют раунд, который может стать последним (решающим) на данной карте (когда одна из сторон выигрывает 15 раундов).
Матч-поинт (Match point) – раунд, который может стать последним (решающим) в данном матче.
Мастерство
Дабл-килл (double kill) — 2 фрага за раунд сделанные одним игроком.
Трипл-килл (triple kill) — 3 фрага за раунд сделанные одним игроком.
Квадро-килл (quadro kill) — 4 фрага за раунд сделанные одним игроком.
Айс/Эйс, ACE (All Clan Elimination) — событие, когда один игрок перестрелял всю команду соперника.
Клатч (clutch – захват, зажатие, тиски) — игровая ситуация, когда один игрок остается против одного или нескольких игроков другой команды.
Клатч-министр – на сленге КС:ГО так называют игрока, который имеет высокий уровень успеха выхода из клатчей (конечно же, наиболее интересны клатчи один против нескольких игроков). Наиболее известные клатчеры:
Ниндзя Дефьюз – незаметное разминирование бомбы «под носом» у невнимательного оппонента.
В соляного (от слова «Соло») — в одиночку разобраться с противником в сложной ситуации.
Сыграть на тонкого — отыгрыш эпизодов с высокой степенью риска, но с определенной перспективой получить выигрыш.
ЛоуТаб (Лоутабер). Low – низкий, Tab – кнопка вызова таблицы с игроками при игре. Лоутабом называют самого нижнего игрока в таблице. Обычно это слабый игрок у которого меньше всех убийств.
Экономика в КС:ГО
Экономика – понятие, которое определяет количество заработанных денег у команды.
Форс-бай (Force buy) — вынужденная или тактическая закупка на все деньги, даже если на полноценный закуп не хватает. Например, раунд перед сменой сторон, когда экономить не имеет смысла. Или после выигранного пистолетного раунда, когда у противника проблемы с деньгами – в этом случае имеет смысл закупить пистолеты-пулеметы или дробовики с целью заработать больше денег.
Фулл бай (Full buy) – максимально полная закупка (оружие, броня и полный комплект гранат).
Тактики и стили игры
Тактик (по тактике) – неспешный академический заход на точку с использованием раскидок, позиционирования, подстраховки. Основная идея – перестрелять противника по одному.
Пуш или Раш (Push, Rush – толкать, прорывать) — штурм противника на какой-либо точке.
Аркада, пуш/раш тактика — агрессивный стиль игры, который заключается в быстром переходе от респауна к атаке противника без долгих тактических раскачек.
Дифэнс (Defense) – оборонительная тактика игры.
Холд, Холдить (Hold – удерживать) – тактика замедления действий и выжидания (например, выжидание спецназом террористов на точке установки бомбы).
Сплит (Split – разрыв, раскол) — тактика разделения команды для подхода на точку с разных направлений.
Места на картах
Длина/Лонг (Long – длинный) – длинный участок на карте с хорошим просмотром и соответственно хорошо простреливаемый. Классическое место для дуэлей снайперов (чаще всего упоминается при игре на картах Dust2, Train, Overpass)
Банан (Banana) – небольшой изогнутый проход. Самый известный банан – это проход на точку B на карте Inferno.
Кишка — узкий длинный проход, коллектор.
Винты — вентиляционные шахты.
Зига, Cat (кэт), Кэтволк — небольшой проход в виде зигзага (Dust2, Mirage, Overpass).
Тёмка, Тэшка, Тоннель (верхняя, нижняя) — тоннели на картах.
Банка, Коробка, Бокс — проход с респауна террористов под лонг Dust2.
Коннектор – один из проходов для террористов с мидла на точку А на карте Mirage.
Зелень, зеленка —коридор на карте Train, ведущий от респауна террористов на точку A. Называется зеленью, так как он покрыт растительностью.
Джангл — снайперская позиция на карте Mirage.
Ответы на популярные вопросы
Что такое «Слон Длина Зажим«. Это игровой сленг из CS 1.6 и CS:GO. Слон – снайперская винтовка AWP (термин использовали игроки в CS 1.6, он подчеркивал, что из такой винтовки можно и слона убить). Длина – место на карте (Dust2, Dust2, Train, Overpass). Зажим – стрельба очередью, когда зажимается левая кнопка мыши.
Кто такие Напы в КС:ГО. Напы – это напарники, режим игры с напарниками.
Кто такой Петрик – комментаторы матчей часто упоминают имя Александра Петрика во время стримов. Александр Петрик – известный профессиональный аналитик по киберспортивной дисциплине CS:GO.
Положительный kell фактор или почему не возьмут цельную кровь
Именно такую фразу можно услышать в пунктах по приему донорской крови с 2002 года благодаря приказу минздрава № 363. Давайте разберемся, что значит этот загадочный kell фактор, и почему людям с этим антигеном нельзя быть донорами крови?
Что такое система Kell—Cellano?
Это система антигенов на поверхности эритроцитов аналогичная системе резус-фактор. Белок XK, который расположен на поверхности эритроцитов связан с этим антигеном и несет о нем информацию. Большинство людей келл отрицательны, и только 8-10% имеют положительный келл. Если перелить цельную кровь K+ человеку у которого K-, то велика вероятность возникновения гемолиза. Говоря простым языком, иммунные клетки реципиента начнут атаковать кровь донора. Ничем хорошим это не закончится.
Именно поэтому, пункты приема донорской крови, не берут цельную кровь от kell+ доноров. Их кровь сможет подойти слишком малому проценту людей. Зато они с легкостью могут сдавать компоненты крови, например, плазму или тромбоциты. Все, что не содержит эритроциты.
Опасно ли для самого человека иметь положительный kell фактор?
На данный момент, не выявлено никаких особенных болезней у людей с kell+. Это просто особенность, такая же как и резус-фактор.
Единственная неприятность может возникнуть, если женщина имеющая отрицательный kell, беременна ребенком с положительным. В некоторых случаях возникает гемолитическая болезнь новорожденных, когда организм матери вырабатывает антитела к эритроцитам плода. Однако это бывает достаточно редко.
Интересный факт: ученые отмечают, что наследование kell+ сцеплено с наследованием возможности ощущать на вкус соединение фенилтиокарбамид. Если человек ощущает его горьким на вкус (а не безвкусным), то он имеет предрасположенность к развитию язвенной болезни желудка.
Чем стратегия для режима выживания Spec Ops должна отличаться от обычного многопользовательского режима?
Сражение с врагами в режиме выживания, безусловно, отличается от тех, которые вы встретите, играя против людей. Какая тактика хорошо работает в режиме выживания, которая иначе не сработает в многопользовательской игре?
Сражение с врагами в режиме выживания, безусловно, отличается от тех, которые вы встретите, играя против людей. Какая тактика хорошо работает в режиме выживания, которая иначе не сработает в многопользовательской игре?
Это должно быть место с верхним навесом, чтобы избежать чопперов, в идеале должно быть не более 2 входов. Мне еще предстоит найти подходящее место на картах с одной точкой входа.
Получите сторожевую гранатомет, пусть ваш партнер сделает то же самое, получите подкрепление отряда Riot Squad,
C4 и Claymores полезны, но каждый раунд их можно купить немного дороже.
Световые гранаты неоценимы для того, чтобы одолеть джаггернаутов. К середине 30-го уровня вы столкнетесь с четырьмя джаггернаутами и взрывающимися собаками в доспехах на большинстве карт. Это может сойти с ума. Я не нашел какой-либо эффективной стратегии вытеснения, вражеские войска будут забивать большинство дверных проемов и узких мест.
Я собираюсь рассказать о некоторых грубых методах выживания, которые я использовал.
У тебя должно быть два часовые, желательно пулеметы, потому что осколки медленные и дымные; на мой взгляд, их сила не так уж и велика. Убедитесь, что часовые все время находятся под крышей, чтобы вертолеты не убили ее. Оставьте часовых сражаться с джаггернаутами.
L86 LSW, самый быстрый пулемет с перезарядкой, является обязательным. Купите его за GRIP после того, как FAD появится на земле. Возьмите M60E4, Red Dot Sight, GRIP как второстепенные, если у вас 30+ патронов.
Самостоятельное возрождение (всегда) и Бронежилет имеет низкий приоритет, но должен быть включен после волны 17. Бунтарь или Дельта щиты, когда они мертвы. (Дельта плохо борется с джаггернаутом, и спецназ сбрасывает щиты, когда они мертвы.)
Хищник Ракета после того, как были развернуты часовые, перк упал, а бунт или дельта-отряд упал, потому что место в серии убийств ограничено одним. Возьми Экстремальное кондиционирование или Ловкость рук льготы. Вы должны заказать два одновременно, чтобы ваш приятель знал, что он получает бонус.
Что вам нужно сделать, так это получить l86lsw с рукояткой и прицелом с красной точкой и acr с прицелом с красной точкой и гранатометом 2 гранатометных сторожевых орудия и отряда по борьбе с беспорядками
18 Jan 2013 в 23:57
18 Jan 2013 в 23:57 #1
18 Jan 2013 в 23:58 #2
18 Jan 2013 в 23:58 #3
18 Jan 2013 в 23:59 #4
18 Jan 2013 в 23:59 #5
19 Jan 2013 в 00:01 #6
19 Jan 2013 в 00:01 #7
лучший K/D/A может быть разный в зависимости от роли героя в игре
19 Jan 2013 в 00:01 #8
19 Jan 2013 в 00:03 #9
В целом можно сказать, по нему надо судить то как ты играешь за персонажа, нежели по винрейту, так как этот показатель, (простите за тавтологию), показывает степень твоей полезности в команде, чем выше, тем ты полезнее.
19 Jan 2013 в 00:04 #10
Наткнулся на тему и решил воспользоваться моментом.
Как считается KDA Ratio на дотабафе?
19 Jan 2013 в 00:07 #11
у меня самый высокий коэф за висажа, за него же и больше всего игр
19 Jan 2013 в 00:07 #12
19 Jan 2013 в 00:08 #13
киллы+ассисты,Как было сказано выше.
19 Jan 2013 в 00:10 #14
И если я правильно понял, чем меньше ратио тем хуже мои дела, так? )
19 Jan 2013 в 00:10 #15
19 Jan 2013 в 00:11 #16
тут и самому догадаться можно (
19 Jan 2013 в 00:11 #17
Главное, чтобы ты хотя бы убивал на 1 больше, чем умер. Ну или ассистил нормально.
19 Jan 2013 в 00:12 #18
если играешь на керри-это вообще нехорошо
Что ученые должны знать о железе для написания быстрого кода
источник изображения
Программирование сегодня используется во многих областях науки, где отдельным ученым часто приходится собственноручно писать код для своих проектов. Для большинства ученых, однако, компьютерные науки не являются их областью знаний; они изучили программирование по необходимости. Я считаю себя одним из них. Хотя мы можем быть достаточно хорошо знакомы с программированием со стороны софта, мы редко имеем даже базовое представление о том, как железо влияет на производительность кода.
Цель этого урока — дать непрофессиональным программистам краткий обзор особенностей современного оборудования, которые нужно понимать, чтобы писать быстрый код. Это будет дистилляция того, что мы узнали за последние несколько лет. Этот учебник будет использовать Julia, потому что она позволяет легко продемонстрировать эти относительно низкоуровневые соображения на высокоуровневом интерактивном языке.
Это не руководство по языку программирования Julia
Чтобы писать быстрый код, вы должны сначала понять свой язык программирования и его особенности. Но это не руководство по языку программирования Julia. Я рекомендую прочитать раздел советы по производительности из документации Julia.
Это не объяснение конкретных структур данных или алгоритмов
Помимо знания вашего языка, вы также должны понимать свой собственный код, чтобы сделать его быстрым. Вы должны понять идею, лежащую в основе нотации big-O, почему некоторые алгоритмы работают быстрее других, и как различные структуры данных работают внутри. Не зная, что такое «массив», как можно оптимизировать код, использующий массивы?
Это тоже выходит за рамки данной статьи. Однако я бы сказал, что, как минимум, программист должен иметь представление о том:
Кроме того, я бы также рекомендовал ознакомиться с такими штуками как:
Это не учебник по бенчмаркингу вашего кода
Чтобы написать быстрый код на практике, необходимо его профилировать, чтобы найти узкие места, где ваша машина проводит большую часть времени. Необходимо сравнить различные функции и подходы, чтобы найти наиболее быстрые на практике. У Джулии (и других языков) есть инструменты именно для этой цели, но я не буду на них заостряться.
Содержание
Прежде чем начать, следует установить пакеты:
Базовая структура компьютерного оборудования
Начнем с упрощенной ментальной модели компьютера. Далее, будем добавлять детали к нашей модели по мере необходимости.
На этой простой диаграмме стрелки представляют поток данных в любом направлении. На диаграмме показаны три важные части компьютера:
Minimize disk writes
Даже при быстром запоминающем устройстве, таком как твердотельный накопитель (SSD) или даже более новая технология Optane, диски во много раз, обычно в тысячи раз, медленнее, чем оперативная память. В частности, медленно происходит переключение на новую точку диска для чтения или записи. Как следствие, запись большого куска данных на диск происходит гораздо быстрее, чем запись множества маленьких кусков.
Поэтому для быстрой работы кода необходимо держать рабочие данные в оперативной памяти и максимально ограничить запись на диск.
Следующий пример служит для иллюстрации разницы в скорости: Первая функция открывает файл, получает доступ к одному байту из файла и снова закрывает его. Вторая функция случайным образом получает доступ к 1 000 000 целых чисел из оперативной памяти.
Бенчмаркинг этого немного сложен, потому что первый вызов будет включать в себя время компиляции обеих функций. А во время второго вызова ваша операционная система сохранит копию файла (или кэширует файл) в оперативной памяти, что сделает поиск файла почти мгновенным. Чтобы правильно рассчитать время, запустите это один раз, затем измените файл на другой, который не был недавно открыт, и запустите это дело снова. Так что на самом деле мы должны обновить нашу схему компьютера:
На моем компьютере поиск одного байта в файле (включая открытие и закрытие файла) занимает около 781 мкс, а доступ к 1 000 000 целых чисел из памяти занимает 95 миллисекунд. Таким образом, оперативная память работает примерно в 8000 раз быстрее, чем диск.
При работе с данными, слишком большими, чтобы поместиться в оперативную память, загружайте данные по частям, например по одной строке за раз, и работайте с ними. Таким образом, вам не нужен произвольный доступ к вашему файлу с тратами на дополнительные поиски, а только последовательный доступ. И вы должны стремиться написать свою программу так, чтобы любые входные файлы считывались только один раз, а не несколько.
Если вам нужно читать файл байт за байтом, например при синтаксическом анализе файла, большого прироста скорости можно добиться с помощью буферизации файла. При буферизации вы считываете большие куски, буферы, в память, а когда вы хотите прочитать из файла, вы проверяете, находится ли он в буфере. Если нет, считайте еще один большой кусок в буфер из файла. Этот подход минимизирует чтение с диска. Однако и ваша операционная система, и ваш язык программирования будут использовать кэши, однако, иногда необходимо вручную буферизировать ваши файлы.
CPU cache
Оперативная память быстрее диска, а процессор, в свою очередь, быстрее оперативной памяти. Процессор тикает, как часы, со скоростью около 3 ГГц, то есть 3 миллиарда тиков в секунду. Один «тик» этих часов называется тактовым циклом. Хотя это не совсем так, вы можете себе представить, что каждый цикл процессор выполняет одну простую команду, называемую инструкцией процессора, которая выполняет одну операцию над небольшим фрагментом данных. Тогда тактовая частота может служить ориентиром для других таймингов в компьютере. Стоит понимать, что за один такт фотон пройдет только около 10 см, и это ставит барьер тому, насколько быстро может работать память (которая расположена на некотором расстоянии от процессора). На самом деле, современные компьютеры настолько быстры, что существенным узким местом в их скорости является задержка, вызванная временем, необходимым для движения электричества по проводам внутри компьютера.
В этом масштабе чтение из оперативной памяти занимает около 100 тактов. Аналогично тому, как медлительность дисков может быть уменьшена путем копирования данных в более быструю оперативную память, данные из оперативной памяти копируются в меньший чип памяти непосредственно на процессоре, называемый кэш. Кэш быстрее, потому что он физически находится на чипе процессора (уменьшая путевые задержки), а также потому, что он использует более быстрый тип оперативной памяти, статическую оперативную память, вместо более медленной (но более дешевой в производстве) динамической оперативной памяти. Поскольку он должен быть размещен на процессоре, ограничивается его размер, и поскольку он более дорог в производстве, типичный кэш процессора содержит только около бит, примерно в 1000 раз меньше, чем оперативная память. На самом деле существует несколько уровней кэша процессора, но здесь мы упрощаем его и просто называем «кэш» как одну единственную вещь:
Когда процессор запрашивает часть данных из оперативной памяти, скажем, один байт, он сначала проверяет, находится ли память уже в кэше. Если да, то он будет читать оттуда. Это происходит намного быстрее чем доступ к оперативной памяти — обычно, всего за несколько тактов. Если нет, то получается промах кэша, и ваша программа будет тормозиться в течение десятков наносекунд, пока ваш компьютер копирует данные из оперативной памяти в кэш.
Невозможно, за исключением языков очень низкого уровня, вручную управлять кэшем процессора. Вместо этого вы должны убедиться, что эффективно используете свой кэш.
Эффективное использование кэша сводится к локальности, временной и пространственной:
Из этой информации можно вывести ряд простых трюков для повышения производительности:
Используйте как можно меньше памяти. Когда ваши данные занимают меньше памяти, более вероятно, что ваши данные будут находиться в кэше. Помните, что процессор может выполнить около 100 небольших операций за время, потраченное впустую на один промах кэша.
При чтении данных из оперативной памяти считывайте их последовательно, так чтобы у вас в основном были следующие данные, которые вы будете использовать в кэше, а не в случайном порядке. На самом деле современные процессоры будут определять, читаете ли вы данные последовательно, и упреждая выбирать предстоящие данные, то есть извлекать следующий фрагмент во время обработки текущего фрагмента, уменьшая задержки, вызванные пропусками кэша.
Обратите внимание на большое расхождение во времени.
Многие из оптимизаций в этом документе косвенно влияют на использование кэша, поэтому это важно иметь в виду.
Alignment
Как уже упоминалось, ваш процессор будет перемещать 512 последовательных бит (64 байта) в основную оперативную память и из нее в кэш одновременно. Эти 64 байта называются строкой кэша. Вся ваша основная память сегментирована на строки кэша. Например, адреса памяти от 0 до 63 — это одна строка кэша, адреса от 64 до 127-следующая, от 128 до 191-следующая и так далее. Ваш процессор может запрашивать только одну из этих строк из памяти, а не, например, 64 байта с адреса 30 до 93.
Это означает, что некоторые структуры данных могут пересекать границы между строками кэша. Если я запрошу 64-битное (8 байтовое) целое число по адресу 60, процессор должен сгенерировать два запроса памяти (а именно, чтобы получить строки кэша 0-63 и 64-127), а затем получить целое число из обеих строк кэша, теряя время.
В приведенном выше примере короткий вектор легко помещается в кэш. Если мы увеличим размер вектора, то заставим кэш промахнуться. Обратите внимание, что эффект несоосности затмевает время, потраченное на промахи кэша:
К счастью, компилятор делает несколько трюков, чтобы уменьшить вероятность того, что вы получите доступ к несогласованным данным. Во-первых, Джулия (и другие компилируемые языки) всегда помещает новые объекты в память на границах строк кэша. Когда объект помещается прямо на границе, мы говорим, что он выровнен. Джулия также выравнивает начало больших массивов:
Обратите внимание, что если начало массива выровнено, то для 1-, 2-, 4- или 8-байтовых объектов невозможно пересечь границы строк кэша, и все будет выровнено.
По-прежнему было бы возможно, например, чтобы 7-байтовый объект был смещен в массиве. В массиве 7-байтовых объектов 10-й объект будет помещен со смещением байта , и объект перешагнет строку кэша. Однако компилятор обычно не допускает структуру с нестандартным размером. Определим 7-байтовую структуру:
Затем мы можем использовать интроспекцию Джулии, чтобы получить относительное положение каждого из трех целых чисел в объекте AlignmentTest в памяти:
Можно заметить, что, несмотря на то, что AlignmentTest имеет только 4 + 2 + 1 = 7 байт фактических данных, он занимает 8 байт памяти, и доступ к объекту AlignmentTest из массива всегда будет выровнен.
Есть только несколько ситуаций, в которых вы можете столкнуться с проблемами выравнивания, как программист. Я могу придумать два варианта:
Inspect generated assembly
Для запуска любая программа должна быть переведена или скомпилирована в инструкции процессора. Инструкции процессора — это то, что на самом деле работает на вашем компьютере, в отличие от кода, написанного на вашем языке программирования, который является просто описанием программы. Инструкции процессора обычно представляются людям в ассемблер-коде. Ассемблер — это язык программирования, который имеет однозначное соответствие с инструкциями процессора.
Просмотр низкоуровнего кода будет полезен для понимания некоторых из следующих разделов, которые относятся к инструкциям процессора.
Быстрая инструкция, медленная инструкция
Не все инструкции процессора одинаково быстры. Ниже приведена таблица избранных инструкций процессора с очень приблизительными оценками того, сколько тактов им требуется для выполнения. Вы можете найти гораздо более подробные таблицы в этом документе. Здесь я кратко опишу скорость выполнения инструкций на современных процессорах Intel. Это очень похоже на все современные процессоры.
Инструкции процессоров обычно занимают несколько циклов процессора для завершения. Однако, если инструкция использует другую часть ЦП во время ее выполнения, ЦП обычно может запустить новую инструкцию до завершения старой: если какая-то операция X занимает, скажем, 4 такта, они могут поставить в очередь одну или даже две операции за такт, используя функцию, называемую конвейеризация инструкций. Следовательно, Инструкция X имеет задержку в 4 цикла, что означает, что для завершения инструкции требуется 4 цикла. Но если процессор может поставить в очередь новую инструкцию в каждый отдельный цикл, он может иметь взаимную пропускную способность 1 такт, что означает в среднем, что требуется только 1 цикл на операцию.
В следующей таблице время измеряется в тактах:
Instruction | Latency | Rec. throughp. |
---|---|---|
move data | 1 | 0.25 |
and/or/xor | 1 | 0.25 |
test/compare | 1 | 0.25 |
do nothing | 1 | 0.25 |
int add/subtract | 1 | 0.25 |
bitshift | 1 | 0.5 |
float multiplication | 5 | 0.5 |
vector int and/or/xor | 1 | 0.5 |
vector int add/sub | 1 | 0.5 |
vector float add/sub | 4 | 0.5 |
vector float multiplic. | 5 | 0.5 |
lea | 3 | 1 |
int multiplic | 3 | 1 |
float add/sub | 3 | 1 |
float multiplic. | 5 | 1 |
float division | 15 | 5 |
vector float division | 13 | 8 |
integer division | 50 | 40 |
Команда lea принимает три входа, A, B и C, где A должно быть 2, 4 или 8, и вычисляет AB + C. Мы вернемся к тому, что делают инструкции «вектор» позже.
Для сравнения мы можем также добавить некоторые очень грубые оценки других источников задержек:
Delay | Cycles |
---|---|
move memory from cache | 1 |
misaligned memory read | 10 |
cache miss | 500 |
read from disk | 5000000 |
Если у вас есть внутренний цикл, выполняющийся миллионы раз, вполне резонно проверить сгенерированный низкоуровневый код для цикла и проверить, можете ли вы выразить вычисления в терминах быстрых инструкций процессора. Например, если у вас есть целое число, которое, как вы знаете, равно 0 или выше, и вы хотите разделить его на 8 (отбрасывая любой остаток), вы можете вместо этого сделать битовый сдвиг, так как битовые сдвиги намного быстрее, чем целочисленное деление:
Однако современные компиляторы довольно умны и часто находят оптимальные инструкции для использования в ваших функциях, чтобы получить тот же результат, например, заменяя целочисленную инструкцию деления idivq на битовое смещение вправо ( shrq ), где это применимо, чтобы быть быстрее. Вам нужно проверить низкоуровневый код самостоятельно, чтобы увидеть:
Minimize allocations
Как уже упоминалось, оперативная память работает намного медленнее, чем кэш процессора. Однако работа оперативной памяти сопряжена с дополнительным недостатком: ваша операционная система (ОС) отслеживает, какой процесс имеет доступ к какой памяти. Если бы каждый процесс имел доступ ко всей памяти, то было бы тривиально сделать программу, которая сканирует вашу оперативную память на наличие секретных данных, таких как банковские пароли, или существовала бы возможность для одной программы, случайно перезаписать память другой. Вместо этого каждому процессу ОС выделяет кучу памяти, и им разрешается только читать или записывать выделенные данные.
Создание новых объектов в оперативной памяти называется выделением, а уничтожение — освобождением. На самом деле, эти процессы не являются созданием или разрушением в прямом смысле, а скорее актом запуска и остановки отслеживания памяти. Память, которая не отслеживается, в конечном итоге будет перезаписана другими данными. Выделение и освобождение занимают значительное количество времени в зависимости от размера объектов, от нескольких десятков до сотен наносекунд на выделение.
В таких языках программирования, как Julia, Python, R и Java, освобождение производится автоматически с помощью программы, называемой сборщиком мусора (GC). Эта программа отслеживает, какие объекты оказываются недоступными программисту, и освобождает их. Например:
Нет никакого способа вернуть исходный массив «[1,2,3] » обратно, он недостижим. Поэтому он просто тратит впустую оперативную память и ничего не делает. Это «мусор». Выделение и освобождение объектов иногда приводит к тому, что GC начинает сканирование всех объектов в памяти и освобождает недостижимые, что вызывает значительное отставание. Вы также можете запустить сборщик мусора вручную:
Следующий пример иллюстрирует разницу во времени, затраченном на функцию, которая выделяет вектор со временем, которое будет выполняться функция просто изменяющая вектор, ничего не выделяя:
На моем компьютере функция выделяющая память в среднем работает более чем в 20 раз медленнее. Это связано с несколькими свойствами кода:
Обратите внимание, что я использовал среднее время вместо медианы, так как для этой функции GC запускает только приблизительно каждый 30-й вызов, но при этом потребляет 30-40 мкс. Все это означает, что производительный код должен сводить выделение ресурсов к минимуму. Обратите внимание, что макрос @btime печатает количество и размер выделений памяти. Эта информация дается потому, что предполагается, что любой программист, который заботится о тестировании своего кода, будет заинтересован в сокращении аллокаций.
Не на все объекты нужно выделять память
Интуитивно может показаться очевидным, что все объекты будучи помещены в оперативную память, должны иметь возможность быть удаленными в любое время программой, и поэтому должны быть выделены в куче. И для некоторых языков, таких как Python, это верно. Однако это не относится к Джулии и другим эффективным компилируемым языкам. Целые числа, например, часто могут быть помещены в стек.
Почему некоторые объекты должны выделяться как куча, а другие могут быть в виде стека? Чтобы быть заточенным под стек, компилятор должен точно знать, что:
Джулия имеет еще больше ограничений на объекты, выделенные стеком.
Что это означает на практике? В Julia это означает, что если вы хотите быстрые выделяемые как стек объекты, то:
Сравним подноготную приведенных выше объектов:
Поскольку битовые типы не должны храниться в куче и могут быть скопированы свободно, то они хранятся inline в массивах. Это означает, что такого рода объекты могут храниться непосредственно в памяти массива. Не-битовые типы имеют уникальную идентичность и расположение в куче. Они отличаются от копий, поэтому не могут быть свободно скопированы, и поэтому массивы содержат ссылку на место памяти в куче, где они хранятся. Доступ к такому объекту из массива тогда означает сначала доступ к массиву, чтобы получить ячейку памяти, а затем доступ к самому объекту, используя эту ячейку памяти. Помимо двойного доступа к памяти, объекты хранятся в куче менее эффективно, а это означает, что больше памяти необходимо скопировать в кэш процессора, что означает больше пропусков кэша. Следовательно, даже при хранении в куче в массиве битовые типы могут храниться более эффективно.
Exploit SIMD vectorization
Настало время еще раз обновить нашу упрощенную компьютерную схему. Процессор работает только с данными, содержащимися в регистрах. Это небольшие слоты фиксированного размера (например, размером 8 байт) внутри самого процессора. Регистр предназначен для хранения одного фрагмента данных, например целого числа или числа с плавающей запятой. Как указано в разделе, посвященном ассемблерному коду, каждая инструкция обычно относится к одному или двум регистрам, содержащим данные, с которыми работает операция:
Для работы со структурами данных размером более одного регистра данные должны быть разбиты на более мелкие части, которые помещаются внутри регистра. Например, при добавлении двух 128-битных целых чисел на моем компьютере:
Малый размер регистров служит узким местом для пропускной способности процессора: он может работать только с одним int/float за раз. Чтобы обойти это, современные процессоры содержат специализированные 256-битные регистры (или 128-битные в старых процессорах, или 512-битные в совершенно новых), которые могут содержать 4 64-битных int/float одновременно, или 8 32-битных целых чисел и т. д. Смущает то, что данные в таких широких регистрах называются «векторами». ЦП имеет доступ к инструкциям, которые могут выполнять различные операции над векторами, оперируя 4 64-битными целыми числами в одной инструкции. Это называется «одна инструкция, несколько данных», SIMD или векторизация. Примечательно, что 4×64-битная операция не то же самое, что 256-битная операция, например, нет переноса между 4 64-битными целыми числами при добавлении двух векторов. Вместо этого 256-битная векторная операция эквивалентна 4 отдельным 64-битным операциям.
Мы можем проиллюстрировать это на следующем примере:
Стоит упомянуть о взаимодействии между SIMD и выравниванием: если серия 256-битных (32-байтовых) нагрузок SIMD не выровнена, то до половины нагрузок могут пересекать границы линий кэша, в отличие от всего лишь 1/8 из 8-байтовых нагрузок. Таким образом, выравнивание является гораздо более серьезной проблемой при использовании SIMD. Поскольку начало массива всегда выровнено, это обычно не является проблемой, но в тех случаях, когда вы не гарантируете, что начнете с выровненной начальной точки, например при матричных операциях, это может иметь существенное значение. В совершенно новых процессорах с 512-битными регистрами проблемы еще хуже, так как размер SIMD совпадает с размером строки кэша, поэтому все нагрузки будут смещены, если будет смещена начальная нагрузка.
Векторизация SIMD, например, 64-битных целых чисел может увеличить пропускную способность почти в 4 раза, поэтому она имеет огромное значение в высокопроизводительном программировании. Компиляторы автоматически векторизуют операции, если это возможно. Что может помешать этой автоматической векторизации?
SIMD нуждается в непрерывной итерации фиксированной длины
Поскольку векторизованные операции работают с несколькими данными одновременно, невозможно прервать цикл в произвольной точке. Например, если 4 64-битных целых числа обрабатываются за один такт, то невозможно остановить цикл SIMD после обработки 3 целых чисел. Предположим, у вас есть такой цикл:
Здесь цикл может закончиться на любой итерации из-за оператора break. Поэтому любая SIMD-инструкция, загруженная для нескольких целых чисел, может работать с данными после того, как цикл разрывается, то есть с данными, которые никогда не должны быть прочитаны. Это было бы неправильным поведением, и поэтому компилятор не может использовать инструкции SIMD.
Хорошее эмпирическое правило заключается в том, что для simd предпочтительны:
Фактически, даже boundschecking, то есть проверка того, что вы не индексируете вне границ вектора, вызывает ветвление. В конце концов, если предполагается, что код вызывает ошибку границ после 3 итераций, даже одна операция SIMD будет неправильной! Чтобы достичь векторизации SIMD, все boundschecks должны быть отключены. Мы можем использовать это для демонстрации влияния SIMD:
SIMD нуждается в цикле, где порядок циклов не имеет значения
SIMD может изменить порядок, в котором обрабатываются элементы массива. Если результат какой-либо итерации зависит от любой предыдущей итерации таким образом, что элементы не могут быть переупорядочены, компилятор обычно не будет векторизировать. Часто, цикл автоматически не векторизован из-за тонкостей, в которых данные перемещаются в регистрах, что означает, что между элементами массива будет некоторая скрытая зависимость памяти.
Возможно, удивительно, что сложение чисел с плавающей запятой может давать разные результаты в зависимости от порядка (т. е. сложение с плавающей запятой не является ассоциативным):
по этой причине суммирование для float не будет автоматически векторизовано:
Однако высокопроизводительные языки программирования обычно предоставляют команду, сообщающую компилятору, что можно переупорядочить цикл, даже для неассоциативных циклов. В Julia этим занимается макрос @simd :
Struct of arrays
Структура памяти, которую мы имеем выше, называется «массив структур», потому что, ну, это массив, заполненный структурами. Вместо этого мы можем структурировать наши 4 объекта от A до D как «структуру массивов». Концептуально это может выглядеть так:
Со следующим распределением памяти для каждого поля:
Выравнивание больше не является проблемой, никакое пространство не тратится впустую на компоновку. При прохождении через поля а все строки кэша содержат полные 64 байта релевантных данных, поэтому операции SIMD не нуждаются в дополнительных операциях для выбора релевантных данных:
Use specialized CPU instructions
Большая часть кода использует типичные инструкции процессора: перемещение, сложение, умножение, битовые сдвиги, и, или, исключающее или, переходы и так далее. Однако процессоры в типичном современном ноутбуке поддерживают множество дополнительных инструкций процессора. Как правило, если определенная операция активно используется в потребительских ноутбуках, производители процессоров добавляют специальные инструкции для ускорения этих операций. В зависимости от аппаратной реализации инструкций, выигрыш в скорости от использования этих инструкций может быть значительным.
Джулия предлагает несколько специальных инструкций, таких как:
Следующий пример иллюстрирует разницу в производительности между ручной реализацией функции count_ones и встроенной версией, которая использует инструкцию popcnt :
Вызов инструкций процессора
Джулия позволяет напрямую вызывать инструкции процессора. Это обычно не рекомендуется, так как не все ваши пользователи будут иметь доступ к одному и тому же процессору с одинаковыми инструкциями.
Последние процессоры содержат специальные инструкции для AES-шифрования и SHA256-хэширования. Если вы хотите вызвать эти инструкции, вы можете использовать бэкэнд-компилятор Julia, LLVM, напрямую. В приведенном ниже примере я создаю функцию, которая непосредственно вызывает инструкцию vaesenc (один проход шифрования AES) :
Алгоритмы, использующие специализированные инструкции, могут быть чрезвычайно быстры. В блоге, компания видеоигр Molly Rocket представила новую некриптографическую хэш-функцию с использованием инструкций AES, которая достигла беспрецедентных скоростей.
Inline small functions
Рассмотрим ассемблирование такой функции:
Однако глянем на низкоуровневый код этой функции:
Так почему же тогда не все функции встроены? Инлайнинг копирует код, увеличивает его размер и потребляет оперативную память. Кроме того, сами инструкции ЦП также должны помещаться в кэш ЦП (хотя инструкции ЦП имеют свой собственный кэш), чтобы быть эффективно извлеченными. Если бы все было встроено, программы стали бы огромными. Инлайнинг хорош, когда встроенная функция мала.
Если код содержит чувствительный ко времени раздел, например внутренний цикл, важно посмотреть на код сборки, чтобы убедиться, что небольшие функции в цикле встроены. Например, в этой строке в моем хешировании kmer code, общая производительность minhashing падает в два раза, если эта аннотация @inline удаляется.
Крайняя разница между инлайнингом и отсутствием инлайнинга может быть продемонстрирована таким образом:
Unroll tight loops
В общей сложности 4 инструкции на элемент вектора. Фактический код, сгенерированный Джулией, будет похож на этот, но также будет включать дополнительные инструкции, связанные с проверкой границ, которые здесь не имеют отношения к делу (и, конечно же, будут включать различные комментарии).
Однако, если функция написана следующим образом:
Результат, очевидно, тот же самый, если мы предположим, что длина вектора делится на четыре. Если длина не делится на четыре, мы можем просто использовать приведенную выше функцию для суммирования первых N — rem(N, 4) элементов и добавления последних нескольких элементов в другой цикл. Несмотря на функционально идентичный результат, код цикла отличается и может выглядеть примерно так:
В общей сложности 7 инструкций на 4 суммирования, или 1,75 инструкции на операцию. Это меньше половины количества инструкций на число integer! Увеличение скорости происходит от того, что проверка выхода заграницы цикла происходит реже. Мы называем этот процесс разворачиванием цикла, в данном случае в четыре раза. Естественно, разворачивание может быть выполнено только в том случае, если мы заранее знаем число итераций, поэтому мы не «перескакиваем» число итераций. Часто компилятор автоматически разворачивает циклы для повышения производительности, но иногда стоит посмотреть на ассемблерный код. Например, вот сборка для самого внутреннего цикла, сгенерированного на моем компьютере для sum([1]) :
Там где можно, он развернут в четыре раза и использует 256-битные инструкции SIMD, в общей сложности 128 байт, 16 целых чисел, суммированных за итерацию, или 0.44 инструкции на целое число.
3 такта), прежде чем процессор сможет начать следующий.
Avoid unpredictable branches
Как упоминалось ранее, инструкции ЦП занимают по несколько циклов, но могут быть поставлены в очередь в ЦП до того, как предыдущая инструкция завершит вычисление. Итак, что же происходит, когда процессор сталкивается с ветвлением (то есть с инструкцией перехода)? Он не может знать, какую инструкцию поставить в очередь следующей, потому что это зависит от инструкции, которую он только что поставил в очередь и которая еще не выполнена.
Современные процессоры используют предсказание ветвей. Центральный процессор имеет схему предсказателя ветвей, которая угадывает правильную ветвь, основываясь на том, какие ветви были недавно взяты. В сущности, предсказатель ветвей пытается изучить простые шаблоны, в которых ветви берутся в коде, пока код выполняется. После постановки ветви в очередь, центральный процессор немедленно ставит в очередь инструкции из любой ветви, предсказанной предсказателем ветви. Правильность предположения проверяется позже, когда выполняется ветвь в очереди. Если предположение было правильным, отлично, процессор экономил время, угадывая. Если нет, ЦП должен очистить конвейер и отбросить все вычисления с момента первоначального предположения, а затем начать все сначала. Этот процесс вызывает задержку в несколько наносекунд.
Для программиста это означает, что скорость if-оператора зависит от того, насколько легко его угадать. Если это тривиально, предсказатель ветвления будет правильным почти все время, и оператор if займет не больше, чем простая инструкция, обычно 1 такт. В ситуации, когда ветвление является случайным, оно будет неверным примерно в 50% случаев, и каждое неверное предсказание может стоить около 10 тактов.
Ветви, вызванные циклами, являются одними из самых простых для угадывания. Если у вас есть цикл с 1000 элементами, код повторит цикл 999 раз и вырвется из цикла только один раз. Следовательно, предсказатель ветвей может просто всегда предсказать «опять по циклу» и получить точность 99,9%.
Мы можем продемонстрировать накладки неправильного предсказания ветвей с помощью простой функции:
В первом случае целые числа случайны, и около половины ветвей будут неверно предсказаны, что приведет к задержкам. Во втором случае ветвь всегда берется, и предсказатель способен быстро подобрать паттерн и достигнет почти 100% правильного предсказания. В результате на моем компьютере последний работает примерно в 6 раз быстрее.
Поскольку ветви очень быстры, если они предсказаны правильно, высокопрогнозируемые ветви, вызванные проверками ошибок, не имеют большого значения для производительности, предполагая, что код по существу никогда не ошибается. Следовательно, ветвь, подобная проверке границ, очень быстра. Вы должны удалять проверки границ только в том случае, если абсолютно максимальная производительность критична, или если проверка границ происходит в цикле, который в противном случае был бы SIMD-векторизован.
Если ветви не могут быть легко предсказаны, часто можно перефразировать функцию, чтобы избежать ветвей. Например, в приведенном выше примере copy_odds! мы могли бы вместо этого написать так:
Тут нет никаких других ветвей, кроме той, которая вызвана самим циклом (что легко предсказуемо), и приводит к скорости несколько хуже, чем идеально предсказанная, но гораздо лучше для случайных данных.
Переменная тактовая частота
Современный процессор ноутбука, оптимизированный для низкого энергопотребления, потребляет примерно 25 Вт мощности на чипе размером со штамп (и тоньше человеческого волоса). Без надлежащего охлаждения это приведет к тому, что температура процессора взлетит до небес и расплавит пластик чипа, разрушив его. Как правило, процессоры имеют максимальную рабочую температуру около 100 C. Энергопотребление, а следовательно, и тепловыделение зависит, помимо прочего, от тактовой частоты — более высокие тактовые частоты генерируют больше тепла.
Современные процессоры способны регулировать свою тактовую частоту в соответствии с температурой процессора, чтобы предотвратить разрушение чипа. Часто температура процессора будет ограничивающим фактором в том, как быстро процессор может работать. В таких ситуациях лучшее физическое охлаждение вашего компьютера напрямую приводит к более быстрому процессору. Старые компьютеры часто можно оживить, просто удалив пыль из салона и заменив охлаждающие вентиляторы и термопасту процессора!
Как программист, вы мало что можете сделать, чтобы учесть температуру процессора, но это полезно знать. В частности, колебания температуры процессора часто объясняют наблюдаемую разницу в производительности:
Multithreading
В старые добрые времена тактовая частота процессора увеличивалась с каждым годом по мере появления на рынке новых процессоров. Частично из-за тепловыделения это ускорение замедлилось, как только процессоры достигли отметки в 3 ГГц. Теперь мы видим лишь незначительные приращения тактовой частоты в каждом поколении процессоров. Вместо необработанной скорости выполнения, фокус сместился на получение большего количества вычислений за такт. Кэши ЦП, конвейеризация ЦП, прогнозирование ветвей и инструкции SIMD — все это важные достижения в этой области, и все они были рассмотрены выше.
Еще одна важная область, в которой процессоры улучшились — это просто их число: почти все процессорные чипы содержат несколько меньших процессоров или ядер внутри. Каждое ядро имеет свой собственный небольшой кэш процессора и выполняет вычисления параллельно. Кроме того, многие процессоры имеют функцию под названием hyper-threading, где два потока (т.е. потоки инструкций) могут работать на каждом ядре. Идея состоит в том, что всякий раз, когда один процесс останавливается (например, из-за того, что он испытывает промах кэша или неправильное предсказание), другой процесс может продолжаться на том же ядре. Процессор «притворяется», что у него в два раза больше процессоров. Например, я пишу это на ноутбуке с процессором Intel Core i9-9880H. Этот процессор имеет 8 ядер, но различные операционные системы, такие как Windows или Linux, будут показывать 16 «процессоров» в программе системного монитора.
Гиперпоточность действительно имеет значение только тогда, когда ваши потоки иногда не могут выполнять работу. Помимо внутренних причин процессора, таких как промахи кэша, поток также может быть приостановлен, потому что он ожидает внешнего ресурса, такого как веб-сервер или данные с диска. Если вы пишете программу, в которой некоторые потоки проводят значительное время на холостом ходу, ядро может быть использовано другим потоком, и hyperthreading может показать свою ценность.
Давайте посмотрим нашу первую параллельную программу в действии. Во-первых, нам нужно убедиться, что Джулия действительно была запущена с правильным количеством потоков. Вы можете установить переменную окружения JULIA_NUM_THREADS перед запуском Julia. У меня есть 8 ядер на этом процессоре, все с гиперпоточностью, поэтому я установил количество потоков на 16:
Видно, что с помощью этой задачи мой компьютер может выполнять 16 заданий параллельно почти так же быстро, как он может выполнять 1. Но 32 задания занимают гораздо больше времени.
Для программ с ограниченным процессором ядро постоянно занято только одним потоком, и программисту не так уж много нужно сделать, чтобы использовать гиперпоточность. На самом деле, для наиболее оптимизированных программ отключение гиперпоточности обычно приводит к повышению производительности. Большинство рабочих нагрузок не настолько оптимизированы и действительно могут извлечь выгоду из гиперпотока, поэтому мы пока остановимся на 16 потоках.
Распараллеливаемость
Многопоточность сложнее, чем любая другая оптимизация, и должна быть одним из последних инструментов, к которым обращается программист. Тем не менее, это также эффективная оптимизация. Вычислительные кластеры обычно содержат процессоры с десятками процессорных ядер, предлагая огромный потенциальный прирост скорости.
Необходимым условием эффективного использования многопоточности является то, что ваши вычисления могут быть разбиты на несколько блоков, над которыми можно работать независимо. К счастью, большинство вычислительных задач (по крайней мере, в моей области работы, биоинформатике) содержат подзадачи, которые до неприличия параллельны. Это означает, что существует естественный и простой способ разбить его на подзадачи, которые могут быть обработаны независимо. Например, если для 100 генов требуется определенное независимое вычисление, естественно использовать один поток для каждого гена.
Множество Жюлиа, который я создаю ниже, определяется следующим образом: мы определяем функцию , где
— некоторая константа. Затем мы записываем количество раз, когда
может быть применено к любому заданному комплексному числу
до
2$» data-tex=»inline»/>. Количество итераций соответствует яркости одного пикселя на изображении. Мы просто повторяем это для диапазона реальных и мнимых значений в сетке, чтобы создать изображение.
Во-первых, давайте посмотрим на непараллельное решение:
Заняло около 10 секунд на моем компьютере. Теперь перейдем к параллельному:
Почти в 16 раз быстрее! С 16 потоками это близко к наилучшему сценарию, возможному только для почти идеальных смущающе параллельных задач.
Несмотря на потенциал больших выгод, на мой взгляд, многопоточность должна быть одним из последних средств повышения производительности по трем причинам:
До сих пор мы рассматривали только самый важный вид вычислительных чипов — центральный процессор. Но есть много других видов чипов. Наиболее распространенным видом альтернативного чипа является графический процессор (GPU).
Как показано в приведенном выше примере с множеством Жюлиа, задачи создания компьютерных образов часто оказываются смущающе параллельными с чрезвычайно высокой степенью распараллеливаемости. В пределе, значение каждого пикселя является независимой задачей. Это требует, чтобы чип с большим количеством ядер работал эффективно. Поскольку создание графики является фундаментальной частью того, что делают компьютеры, почти все коммерческие компьютеры содержат графический процессор. Часто это меньший чип, встроенный в материнскую плату (интегрированная графика, популярная в небольших ноутбуках). В других случаях это большая, громоздкая карта.
Графические процессоры пожертвовали многими наворотами процессоров, описанных в этом документе, такими как специализированные инструкции, SIMD и прогнозирование ветвей. Они также обычно работают на более низких частотах, чем CPU. Это означает, что их необработанная вычислительная мощность во много раз медленнее, чем у процессора. Чтобы компенсировать это, у них есть большое количество ядер. Например, высокопроизводительный игровой графический процессор NVIDIA RTX 2080Ti имеет 4.352 ядра. Следовательно, некоторые задачи могут испытывать 10-кратное или даже 100-кратное ускорение с помощью графического процессора. В частности, для научных приложений матричные и векторные операции обладают высокой степенью распараллеливаемости.
К сожалению, ноутбук, на котором я пишу этот документ, имеет только интегрированную графику, и пока нет стабильного способа взаимодействия с интегрированной графикой с помощью Julia, поэтому я не могу показать примеры.
Существуют также более эзотерические чипы, такие как TPU (явно предназначенные для низкоточных тензорных операций, распространенных в глубоком обучении) и ASICs (общий термин для узкоспециализированных чипов, предназначенных для одного приложения). На момент написания статьи эти чипы были необычными, дорогими, плохо поддерживаемыми и имели ограниченное применение, и поэтому не представляли никакого интереса для исследователей, не связанных с компьютерными науками.