пример паскалевского кода закладки данных в кэш и последующего обращения к ним,
Для Паскаля и любых языков процессор - чёрный ящик. Мы лишь примерно представляем принципы его работы, но влиять на его решения можем лишь опосредованно.
Но опосредованно - не значит неэффективно.
Основной принцип, общий что для Паскаля. что для сиплюсплюс, туз- он и в Африке туз: чтение/запись памяти не должно скакать по всему адресному пространству. Желательно, чтобы данные по которым идёт активная работа располагались компактно в одной или нескольких областях памяти, влезающих в кеш первого уровня. А это - около 64 килобайт на все потоки всех процессов.
Т.е. желательно окучивать данные кусками, активно топчась на пятачках в пару-тройку килобайт размером и гораздо реже переключаясь между такими пятачками.
Строгое следование принципам ООП очень способствует дружественности к кешу, поскольку все данные компактно собраны в экземпляре класса. Переключение на работу с другим объектом как раз и будет стоить примерно одно-два-немножко обновлений кеша, поскольку между кешем и памятью данные перекачиваются кусками в точно не помню, ЕМНИП по 64 байта. В 32 бтном приложении класс, имеющий 15 полей типа Longint, полностью влезет в одну линейку кеша. Конечно, такие маленькие классы в реале редкость, но принцип остаётся.
Аналогично, маленькие процедуры с небольшим числом локальных переменных - это добро, ибо размещаются в стеке, а головка стека уютно сидит в кеше, почти никогда не сползая.
Зло, это:
- глобальные переменные, когда их много и значительный объём данных храни в них, и/или когда в программе уйма модулей, хранящих данные в них
- хранение данных в нескольких массивах, допустим у тебя есть точки, и координаты x хранятся в одном, координаты y - в другом, а цвет - в третьем.
- глубокая рекурсия, когда вызовов столько, что локальные переменные уплывают из кеша.
Далее, на примере программной обработки картинки.
Если планируются операции типа блюра большого радиуса, то лучше хранить картинку не в двумерном массиве ширина х высота, а разбить её на квадратные куски весом не более 1..4 килобайт. Для 8 бит RGBA это будет 32х32. И тогда операцию блюра выполняешь не тупым проходом двумя циклами по х и y, а проходом по этим 32х32 чанкам, внутри которых уже по x и y. Для удобства понадобится чтение и запись пиксела завернуть в виртуальные методы (чтобы адресовать x, y к нужному чанку), но это всё равно быстрее.
Далее, если радиус блюра - огромный, типа 100, то внутри чанка тоже нельзя проходить по x, y. Надо проходить каждый из попадающих в радиус чанков по очереди. Для этого (чтобы не получить каку с округлением) можно завести локальную переменную - 16 или 32 битный буфер-аккумулятор, в который суммировать значения, собираемые с соседних чанков по одному чанку, а ужать до 8 бит и записать в свой чанк - в конце операции.
Я могу быть не прав насчёт такой структуры, но факт остаётся: при операциях "многие влияют на многие" надо елозить по памяти не как удобно, а чтобы елозилось по ограниченным её участкам, медленно смещаясь. Например, можно *не* разбивать на чанки, хранить картинку в двумерном массиве, но принципы обхода памяти циклами должны быть как если бы разбиение на чанки было. Не два вложенных цикла, а четыре, и т.п.
Добавлено спустя 15 минут 42 секунды:З.Ы. Когда надо над многими объектами выполнить несколько операци, то:
Дружественно: o1.A(); o1.B(); o1.C(); o2.A(); o2.B(); o2.C(); ...
Недружественно: o1.A(); o2.A(); ... ;o1.B(); o2.B(); ... ; o1.C(); o2.C(); ...
Конечно, если операция B() зависит от результата A() не только своего, но и других объектов - то уже никуда не денешься, придётся делать недружественно.