Что такое «кеширующий» код?

В чем разница между « недружественным кодом кеша » и « кеш-дружественным » кодом?

Как я могу убедиться, что я написал код, эффективный для кэширования?

прелиминарии

На современных компьютерах только структуры памяти наименьшего уровня ( регистры ) могут перемещать данные за один такт. Тем не менее, регистры очень дороги, и большинство компьютерных ядер имеют менее нескольких десятков регистров (от нескольких сотен до, возможно, тысячи байтов ). На другом конце спектра памяти ( DRAM ) память очень дешевая (т.е. буквально в миллионы раз дешевле ), но занимает несколько сотен циклов после запроса на получение данных. Для преодоления этого разрыва между супер быстрыми и дорогостоящими и супер медленными и дешевыми являются кеш-память , названная L1, L2, L3 в снижении скорости и стоимости. Идея заключается в том, что большая часть исполняемого кода часто попадает на небольшой набор переменных, а остальная часть (гораздо больший набор переменных) нечасто. Если процессор не может найти данные в кеше L1, он выглядит в кэше L2. Если нет, то кеш L3, а если нет, то основная память. Каждый из этих «промахов» дорог во времени.

(Аналогия – это кэш-память для системной памяти, поскольку системная память предназначена для хранения на жестком диске. Жесткий диск очень дешев, но очень медленный).

Кэширование является одним из основных методов снижения воздействия латентности . Перефразируя Herb Sutter (см. Ссылки ниже): увеличение пропускной способности – это просто, но мы не можем купить наш выход из задержек .

Данные всегда извлекаются через иерархию памяти (наименьшая – от самой быстрой до самой медленной). Кэш-пропуск / промах обычно относится к хиту / пропуску на самом высоком уровне кеша в ЦП – на самом высоком уровне я имею в виду самый большой == самый медленный. Частота кэширования имеет решающее значение для производительности, поскольку каждый промах кеша приводит к извлечению данных из ОЗУ (или, что еще хуже …), что занимает много времени (сотни циклов для ОЗУ, десятки миллионов циклов для HDD). Для сравнения, чтение данных из кэша (самого высокого уровня) обычно занимает всего несколько циклов.

В современных компьютерных архитектурах узкое место производительности покидает процессорную матрицу (например, доступ к ОЗУ или выше). Со временем это будет только ухудшаться. Увеличение частоты процессора в настоящее время больше не имеет отношения к повышению производительности. Проблема заключается в доступе к памяти. В настоящее время усилия по проектированию оборудования в процессорах в основном направлены на оптимизацию кэшей, предварительной выборки, конвейеров и параллелизма. Например, современные процессоры тратят около 85% от кеша и до 99% для хранения / перемещения данных!

На эту тему многое можно сказать. Вот несколько замечательных ссылок на кеши, иерархии памяти и правильное программирование:

  • Страница Agner Fog . В его превосходных документах вы можете найти подробные примеры, охватывающие языки, начиная от сборки и заканчивая C ++.
  • Если вы делаете видеоролики, я настоятельно рекомендую взглянуть на рассказ Херба Саттера об архитектуре машины (youtube) (в частности, проверить 12:00 и далее!).
  • Слайды по оптимизации памяти Кристером Эриксоном (директор технологии @ Sony)
  • Статья LWN.net « Что каждый программист должен знать о памяти »

Основные понятия для кэширующего кода

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

Следующие важные аспекты имеют большое значение для оптимизации кэширования:

  1. Временная локальность : при доступе к определенному местоположению памяти, скорее всего, в ближайшем будущем снова будет доступно одно и то же местоположение. В идеале эта информация по-прежнему будет кэшироваться в этот момент.
  2. Пространственная локальность : это относится к размещению связанных данных близко друг к другу. Кэширование происходит на многих уровнях, а не только в CPU. Например, когда вы читаете из ОЗУ, обычно выбирается больший fragment памяти, чем тот, который был задан специально, потому что очень часто программа потребует данных в ближайшее время. Кассеты жестких дисков следуют одной и той же мысли. В частности, для кэшей CPU важно понятие линий кэша .

Используйте соответствующие контейнеры c ++

Простой пример кэширования против кэша – недружелюбный – это std::vector сравнении с std::list . Элементы std::vector хранятся в непрерывной памяти, и поскольку их доступ к ним гораздо более безопасен для кэширования, чем доступ к элементам в std::list , который сохраняет свой контент повсюду. Это связано с пространственной локальностью.

Очень приятная иллюстрация этого дается Bjarne Stroustrup в этом клипе youtube (спасибо @Mohammad Ali Baydoun за ссылку!).

Не игнорируйте кеш в структуре данных и алгоритме

По возможности старайтесь адаптировать свои структуры данных и порядок вычислений таким образом, чтобы максимально использовать кеш. Общей методикой в ​​этом отношении является блокировка кеша (версия Archive.org) , которая имеет чрезвычайно важное значение для высокопроизводительных вычислений (например, ATLAS ).

Знать и использовать неявную структуру данных

Другим простым примером, который иногда забывают многие люди в этой области, является колонка-майор (например, fortran , matlab ) по сравнению с строковым упорядочением (например, c , c ++ ) для хранения двумерных массивов. Например, рассмотрим следующую матрицу:

 1 2 3 4 

В порядке упорядочения строк это сохраняется в памяти как 1 2 3 4 ; в порядке упорядочения столбцов это будет храниться как 1 3 2 4 . Легко видеть, что реализации, которые не используют этот порядок, быстро справятся с проблемами кеша (легко избежать!). К сожалению, я часто вижу такие вещи в моем домене (машинное обучение). @MatteoItalia показал этот пример более подробно в своем ответе.

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

Для простоты предположим, что кеш содержит одиночную строку кэша, которая может содержать 2 элемента матрицы и что, когда данный элемент извлекается из памяти, следующий тоже. Скажем, мы хотим взять сумму по всем элементам в приведенной выше матрице 2×2 (давайте назовем ее M ):

Использование заказа (например, изменение индекса столбца сначала в c ++ ):

 M[0][0] (memory) + M[0][1] (cached) + M[1][0] (memory) + M[1][1] (cached) = 1 + 2 + 3 + 4 --> 2 cache hits, 2 memory accesses 

Не использовать порядок (например, сначала изменить индекс строки в c ++ ):

 M[0][0] (memory) + M[1][0] (memory) + M[0][1] (memory) + M[1][1] (memory) = 1 + 3 + 2 + 4 --> 0 cache hits, 4 memory accesses 

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

Избегайте непредсказуемых ветвей

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

Это объясняется здесь очень хорошо (спасибо @ 0x90 за ссылку): Почему быстрее обрабатывать отсортированный массив, чем несортированный массив?

Избегайте виртуальных функций

В контексте c ++ virtual методы представляют собой спорную проблему в отношении промахов в кеше (существует общий консенсус, который следует избегать, когда это возможно, с точки зрения производительности). Виртуальные функции могут вызвать промахи в кэше во время поиска, но это происходит только в том случае, если конкретная функция не вызывается часто (в противном случае она, скорее всего, будет кэширована), поэтому некоторые из них рассматриваются как не связанные с проблемой. Для справки об этой проблеме проверьте: Какова стоимость выполнения виртуального метода в classе C ++?

Общие проблемы

Общей проблемой в современных архитектурах с многопроцессорными кэшами называется ложное совместное использование . Это происходит, когда каждый отдельный процессор пытается использовать данные в другой области памяти и пытается сохранить их в одной и той же строке кэша . Это приводит к тому, что строка кэша, которая содержит данные, которые может использовать другой процессор, перезаписывается снова и снова. Эффективно разные streamи ожидают друг друга, вызывая ошибки в этой ситуации. См. Также (спасибо @Matt за ссылку): Как и когда выровнять размер строки кеша?

Крайним симптомом плохого кэширования в оперативной памяти (что, вероятно, не то, что вы имеете в виду в этом контексте), является так называемое избиение . Это происходит, когда процесс непрерывно генерирует ошибки страницы (например, обращается к памяти, которая не находится на текущей странице), которая требует доступа к диску.

В дополнение к ответу @Marc Claesen, я считаю, что поучительный classический пример кэширования – недружелюбный код – это код, который сканирует бимодальный массив C (например, bitmap) по столбцам, а не по строкам.

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

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

И все, что требуется для разрушения производительности, – это перейти от

 // Cache-friendly version - processes pixels which are adjacent in memory for(unsigned int y=0; y 

в

 // Cache-unfriendly version - jumps around in memory for no good reason for(unsigned int x=0; x 

Этот эффект может быть довольно драматичным (несколько порядков величин в скорости) в системах с небольшими кэшами и / или работать с большими массивами (например, изображения с частотой 10+ мегапикселей 24 bpp на текущих машинах); по этой причине, если вам нужно делать много вертикальных сканирований, часто лучше сначала поворачивать изображение на 90 gradleусов, а затем выполнять различные анализы, ограничивая кеш-недружественный код только rotationм.

Оптимизация использования кеша во многом сводится к двум факторам.

Местность ссылки

Первый фактор (к которому другие уже упоминали) – это локальность ссылки. Местность ссылки действительно имеет два измерения: пространство и время.

  • пространственная

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

Во-вторых, мы хотим, чтобы информация, которая будет обрабатываться вместе, также расположена вместе. Типичный кеш работает в «строках», что означает, что при доступе к некоторой информации другая информация в соседних адресах будет загружена в кеш с той частью, которую мы коснулись. Например, когда я касаюсь одного байта, кеш может загружать 128 или 256 байтов рядом с этим. Чтобы воспользоваться этим, вы, как правило, хотите, чтобы данные упорядочивались, чтобы максимизировать вероятность того, что вы также будете использовать другие данные, которые были загружены одновременно.

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

  • Время

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

Поскольку вы отметили это как C ++, я std::valarray на classический пример относительно std::valarray дизайна, не std::valarray : std::valarray . valarray перегружает большинство арифметических операторов, поэтому я могу (например) сказать a = b + c + d; (где a , b , c и d – все valarrays), чтобы поэтапно добавлять эти массивы.

Проблема в том, что он проходит через одну пару входов, ставит результаты во временное, проходит через другую пару входов и так далее. При большом количестве данных результат из одного вычисления может исчезнуть из кеша, прежде чем он будет использован в следующем вычислении, поэтому мы закончим чтение (и запись) данных повторно, прежде чем мы получим наш окончательный результат. Если каждый элемент конечного результата будет чем-то вроде (a[n] + b[n]) * (c[n] + d[n]); , мы обычно предпочитали читать каждый a[n] , b[n] , c[n] и d[n] один раз, выполнять вычисления, записывать результат, увеличивать n и повторять, пока мы не закончим. 2

Совместное использование строк

Второй важный фактор – избежать совместного использования линии. Чтобы понять это, нам, вероятно, нужно создать резервную копию и немного посмотреть, как организованы кэши. Простейшая форма кеша напрямую сопоставляется. Это означает, что один адрес в основной памяти может храниться только в одном конкретном месте в кеше. Если мы используем два элемента данных, которые сопоставляются с одним и тем же местом в кеше, он работает плохо – каждый раз, когда мы используем один элемент данных, другой должен быть сброшен из кеша, чтобы освободить место для другого. Остальная часть кеша может быть пуста, но эти элементы не будут использовать другие части кеша.

Чтобы предотвратить это, большинство кешей – это то, что называется «set associative». Например, в четырехпозиционном ассоциативно-ассоциативном кеше любой элемент из основной памяти может быть сохранен в любом из 4-х разных мест в кеше. Таким образом, когда кеш будет загружать элемент, он ищет наименее недавно используемый 3 элемента среди этих четырех, сбрасывает его в основную память и загружает новый элемент на свое место.

Проблема, вероятно, довольно очевидна: для кэша с прямым отображением два операнда, которые случайно совпадают с одним и тем же местоположением кеша, могут привести к плохому поведению. N-way set-ассоциативный кеш увеличивает число от 2 до N + 1. Организация кеша на более «путях» требует дополнительной схемы и, как правило, работает медленнее, поэтому (например) 8192-way set ассоциативный кеш редко является хорошим решением.

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

  • Ложное разделение

Есть еще один связанный элемент, называемый «ложным совместным использованием». Это возникает в многопроцессорной или многоядерной системе, где два (или более) процессора / ядра имеют отдельные данные, но попадают в одну и ту же строку кэша. Это заставляет два процессора / ядра координировать их доступ к данным, хотя каждый из них имеет свой собственный отдельный элемент данных. Особенно, если эти два изменят данные в чередовании, это может привести к значительному замедлению, поскольку данные должны постоянно перекачиваться между процессорами. Это невозможно легко вылечить, организовав кэш более «путями» или что-то в этом роде. Основной способ предотвратить это – убедиться, что два streamа редко (желательно никогда) не изменяют данные, которые могут быть в одной строке кэша (с теми же предостережениями о сложности управления адресами, на которых распределяются данные).


  1. Те, кто хорошо знает C ++, могут задаться вопросом, может ли это быть оптимизировано с помощью шаблонов выражений. Я уверен, что ответ: да, это можно сделать, и если бы это было так, это, вероятно, было бы довольно существенной победой. Тем не менее, я не знаю никого, кто сделал это, и, учитывая, как мало используется valarray , я был бы хоть немного удивлен, увидев, что кто-то это сделает.

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

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

Добро пожаловать в мир Data Oriented Design. Основная мантра – Сортировка, Устранение Ветви, Пакет, Устранение virtual вызовов – все шаги в направлении улучшения местоположения.

Поскольку вы отметили вопрос на C ++, вот обязательная типичная C ++ Bullshit . Топики Тони Альбрехта по объектно-ориентированному программированию также являются отличным введением в тему.

Просто накладывается: classический пример кэширования – недружелюбный и кэширующий код – это «блокировка кеша» матрицы.

Наивное матричное умножение выглядит

 for(i=0;i 

Если N велико, например, если N * sizeof(elemType) больше размера кэша, то каждый отдельный доступ к src2[k][j] будет отсутствовать в кэше.

Существует множество способов оптимизации этого для кеша. Вот очень простой пример: вместо чтения одного элемента в строке кэша во внутреннем цикле используйте все элементы:

 int itemsPerCacheLine = CacheLineSize / sizeof(elemType); for(i=0;i 

Если размер строки кеша составляет 64 байта, и мы работаем с 32-битными (4 байтовыми) поплавками, то в каждой строке кэша должно быть 16 элементов. И количество пропусков кеша через простое преобразование сокращается примерно в 16 раз.

Преобразования Fancier работают с 2D-плитами, оптимизируются для нескольких кешей (L1, L2, TLB) и т. Д.

Некоторые результаты «блокировки кеша» в Google:

http://stumptown.cc.gt.atl.ga.us/cse6230-hpcta-fa11/slides/11a-matmul-goto.pdf

http://software.intel.com/en-us/articles/cache-blocking-techniques

Хорошая видеоадаптация оптимизированного алгоритма блокировки кеша.

http://www.youtube.com/watch?v=IFWgwGMMrh0

Плитка петли очень тесно связана:

http://en.wikipedia.org/wiki/Loop_tiling

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

Логически, в набор инструкций CPU вы просто ссылаетесь на адреса памяти в гигантском виртуальном адресном пространстве. Когда вы обращаетесь к одному адресу памяти, CPU будет его извлекать. в прежние времена он получал бы только один адрес. Но сегодня процессор будет получать кучу памяти вокруг бит, который вы просили, и скопировать его в кеш. Он предполагает, что если вы попросите указать конкретный адрес, который, скорее всего, вы скоро попросите адрес поблизости. Например, если вы копировали буфер, который вы читали бы и записывали бы из последовательных адресов – один за другим.

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

Кэширующий код пытается сохранить доступ в памяти, чтобы минимизировать промахи в кэше.

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

Если вы скопировали элементы по одной строке за раз слева направо – это было бы удобно для кэша. Если вы решили скопировать таблицу по одному столбцу за раз, вы скопируете тот же самый объем памяти, но это будет недружественный кэш.

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

Как правило, чем плотнее код, тем меньше требуется для его хранения. Это приводит к тому, что для данных доступно больше строк кэша.

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

Функция должна начинаться с адреса, подходящего для выравнивания строк в кеше. Хотя есть (gcc) ключи компилятора для этого, имейте в виду, что если функции очень короткие, может быть расточительным для каждого из них, чтобы занять всю строку кэша. Например, если три из наиболее часто используемых функций вписываются в одну строку с байтом в 64 байта, это менее расточительно, чем если каждая из них имеет свою собственную строку и приводит к двум линиям кэша, менее доступным для другого использования. Типичным значением выравнивания может быть 32 или 16.

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

Поскольку @Marc Claesen упомянул, что одним из способов написания кэширующего кода является использование структуры, в которой хранятся наши данные. В дополнение к этому другим способом написания кода, удобного для чтения, является: изменение способа хранения наших данных; затем напишите новый код для доступа к данным, хранящимся в этой новой структуре.

Это имеет смысл в том случае, когда системы баз данных линеаризуют кортежи таблицы и сохраняют их. Существует два основных способа хранения кортежей таблицы: хранилище строк и хранилище столбцов. В хранилище строк, как следует из названия, кортежи хранятся в строке. Предположим, что таблица с именем « Product , хранящаяся» имеет 3 атрибута, то int32_t key, char name[56] и int32_t price , поэтому общий размер кортежа составляет 64 байта.

Мы можем моделировать выполнение очень простого запроса хранилища строк в основной памяти путем создания массива структур Product с размером N, где N – количество строк в таблице. Такой макет памяти также называется массивом структур. Таким образом, структура для продукта может быть такой:

 struct Product { int32_t key; char name[56]; int32_t price' } /* create an array of structs */ Product* table = new Product[N]; /* now load this array of structs, from a file etc. */ 

Аналогичным образом мы можем моделировать выполнение основного запроса хранилища столбцов в основной памяти путем создания 3 массивов размера N, одного массива для каждого атрибута таблицы Product . Такой макет памяти также называется структурой массивов. Таким образом, 3 массива для каждого атрибута Product могут выглядеть так:

 /* create separate arrays for each attribute */ int32_t* key = new int32_t[N]; char* name = new char[56*N]; int32_t* price = new int32_t[N]; /* now load these arrays, from a file etc. */ 

Теперь после загрузки как массива структур (Row Layout), так и 3 отдельных массивов (компоновка столбцов) у нас есть хранилище строк и хранилище столбцов в нашей таблице. Product представлен в нашей памяти.

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

 SELECT SUM(price) FROM PRODUCT 

Для хранилища строк мы можем преобразовать вышеуказанный SQL-запрос в

 int sum = 0; for (int i=0; i 

Для хранилища столбцов мы можем преобразовать вышеуказанный SQL-запрос в

 int sum = 0; for (int i=0; i 

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

Предположим, что размер строки кеша составляет 64 байта.

В случае разметки строк при чтении строки кэша, считывается значение цены только 1 ( cacheline_size/product_struct_size = 64/64 = 1 ) кортежа, потому что наш размер структуры составляет 64 байта, и он заполняет нашу всю строку кеша, поэтому для каждого кортежа провал кеша возникает в случае макета строки.

В случае компоновки столбцов при чтении строки кэша считывается значение цены 16 ( cacheline_size/price_int_size = 64/4 = 16 ) кортежей, потому что 16 смежных значений цен, хранящихся в памяти, приводятся в кеш, поэтому для каждого шестнадцатый кортеж кеша пропускает ocurs в случае расположения столбца.

Таким образом, макет столбца будет быстрее в случае заданного запроса и быстрее в таких агрегационных запросах в подмножестве столбцов таблицы. Вы можете попробовать этот эксперимент для себя, используя данные из теста TPC-H , и сравнить время выполнения для обоих макетов. Статья в википедии о системах баз данных, ориентированных на колонку, также хороша.

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

Имейте в виду, что кэши не просто кэшируют непрерывную память. Они имеют несколько линий (по крайней мере 4), поэтому прерывистая и перекрывающаяся память часто может храниться так же эффективно.

То, что отсутствует во всех приведенных выше примерах, является измеренным эталоном. Существует много мифов о производительности. Если вы не измеряете это, вы не знаете. Не усложняйте свой код, если у вас нет заметного улучшения.

  • Метод android finish () не очищает приложение из памяти
  • Что такое непрерывный блок памяти?
  • Сохранять цикл на `self` с блоками
  • Почему компиляторы позволяют строковым литералам не быть const?
  • Уменьшение использования памяти приложений .NET?
  • Redis в 10 раз больше использования памяти, чем данные
  • получить системную информацию на уровне ОС
  • Кто удаляет память, выделенную во время «новой» операции, которая имеет исключение в конструкторе?
  • Определить размер кучи приложения в Android
  • Выравнивание памяти в C-структурах
  • Изменение размера массива с помощью C
  • Interesting Posts

    Вызвано: java.lang.OutOfMemoryError: размер растрового изображения превышает бюджет VM

    SSD очень медленный на ПК

    Возможно открытие всплывающих ссылок в UIWebView?

    При экспорте отчета в PDF смените шрифт

    Ошибка установки рабочей станции VMWare: MSI '' не удалось

    Chrome: Ctrl-K для поиска в Google и Ctrl-L для меня, я чувствую себя счастливым

    Regex, чтобы получить слова после соответствующей строки

    Безопасно ли использовать блок питания с другим ноутбуком?

    Алгоритм упрощения десятичных дробей

    Почему дополнительная константа автоматически не имеет значения по умолчанию nil

    Является ли это правильным способом очистки стопы fragmentа при выходе из глубоко вложенного стека?

    Рабочий пример CreateJobObject / SetInformationJobObject pinvoke в .net?

    Выберите символ новой строки в Notepad ++

    Возможно ли запустить старое 16-разрядное приложение DOS под 64-разрядной версией Windows 7?

    Подсветка клавиатуры в диспетчере устройств?

    Давайте будем гением компьютера.