Как каждый пишет код, который лучше всего использует кэш ЦП для улучшения производительности?

Вы можете использовать для этого iframe - посмотрите также addResourcePath:

ui = fluidPage(
  titlePanel("opening web pages"),
  sidebarPanel(selectInput(
    inputId = 'test',
    label = 1,
    choices = c("sample", "zoo", "product")
  )),
  mainPanel(htmlOutput("inc"))
)

server = function(input, output) {
  myhtmlfilepath <- getwd() # change to your path
  addResourcePath('myhtmlfiles', myhtmlfilepath)

  getPage <- function() {
    return(tags$iframe(src = paste0("myhtmlfiles/", input$test, ".html"), height = "100%", width = "100%", scrolling = "yes"))
  }

  output$inc <- renderUI({
    req(input$test)
    getPage()
  })
}

shinyApp(ui, server)
.
155
задан mike 1 August 2015 в 13:04
поделиться

11 ответов

Я рекомендую прочитать статью из 9 частей Что каждый программист должен знать о памяти Ульриха Дреппера если вы заинтересованы в том, как взаимодействуют память и программное обеспечение. Он также доступен в виде и 104-страничного PDF .

Разделы, особенно относящиеся к этому вопросу, могут быть Часть 2 (кэши ЦП) и Часть 5 (Что программисты могут сделать - оптимизация кеша).

44
ответ дан 23 November 2019 в 21:55
поделиться

Основные правила на самом деле довольно просты. Трудно понять, как они применяются к вашему коду.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Обычно вам нужно беспокоиться о кеше данных.

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

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

Обычно вам нужно беспокоиться о кеше данных.

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

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

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

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

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

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

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

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

45
ответ дан 23 November 2019 в 21:55
поделиться

Напишите вашу программу, чтобы взять минимальный размер. Вот почему не всегда хорошая идея использовать оптимизацию -O3 для GCC. Это занимает больший размер. Часто -Os так же хорошо, как -O2. Все зависит от используемого процессора. YMMV.

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

Чтобы помочь вам лучше использовать временную / пространственную локальность команд, вы можете изучить, как ваш код преобразуется в сборку. Например:

for(i = 0; i < MAX; ++i)
for(i = MAX; i > 0; --i)

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

1
ответ дан 23 November 2019 в 21:55
поделиться

Кэш размещается в «строках кэша», и (реальная) память считывается и записывается в блоки такого размера.

Следовательно, структуры данных, содержащиеся в одной строке кэша, более эффективны.

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

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

4
ответ дан 23 November 2019 в 21:55
поделиться

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

4
ответ дан 23 November 2019 в 21:55
поделиться

Помимо шаблонов доступа к данным, основным фактором в коде, удобном для кеширования, является размер данных . Меньше данных означает, что их больше помещается в кеш.

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

Точно так же в логическом массиве Java для каждого значения используется целый байт, что позволяет напрямую работать с отдельными значениями. Вы можете уменьшить размер данных в 8 раз, если используете фактические биты, но тогда доступ к отдельным значениям становится намного более сложным, требуя операций сдвига бит и масок (класс BitSet делает это за вас). Однако из-за эффектов кеширования это все еще может быть значительно быстрее, чем использование boolean [], когда массив большой. IIRC I однажды таким образом добился ускорения в 2 или 3 раза.

15
ответ дан 23 November 2019 в 21:55
поделиться

Не могу поверить, что на это больше нет ответов. В любом случае, одним из классических примеров является итерация многомерного массива «наизнанку»:

pseudocode
for (i = 0 to size)
  for (j = 0 to size)
    do something with ary[j][i]

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

for (i = 0 to size)
  for (j = 0 to size)
    do something with ary[i][j]

Он будет работать намного быстрее.

54
ответ дан 23 November 2019 в 21:55
поделиться

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

Я предлагаю прочитать об Оптимизации, на этом сайте есть несколько хороших ответов. С точки зрения книг, я рекомендую в «Компьютерные системы: перспектива программиста» , в котором есть небольшой текст о правильном использовании кэша.

(кстати, как плохо, как может не хватать кеша, еще хуже - если программа подает страницу с жесткого диска ...)

4
ответ дан 23 November 2019 в 21:55
поделиться

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

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

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

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

9
ответ дан 23 November 2019 в 21:55
поделиться

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

7
ответ дан 23 November 2019 в 21:55
поделиться

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

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

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

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

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

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

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

    Объединение циклов, затрагивающих одни и те же данные ( объединение циклов ),

    119
    ответ дан 23 November 2019 в 21:55
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: