Просмотр сообщений

В этом разделе можно просмотреть все сообщения, сделанные этим пользователем.


Сообщения - Valery Solovey

Страницы: 1 [2] 3 4 ... 34
16
Это связано с архитектурой x86. Там для сдвигов влево и вправо используются разные команды. При этом, величина сдвига помещается в регистр cl (8 бит), но процессор учитывает только младшие 5 бит. Т. е. величина сдвига всегда в диапазоне 0..31. Не знаю как в ARM, но в реализации Astrobe тоже есть LSR.
Так это можно решить на этапе трансляции. При чтении второго параметра проверить его на знак и в зависимости от знака подставить правильную команду процессора. А в языке программирования будет только одна предопределённая процедура.
Другое дело, что Left Shift на -3 позиции заставляет мозг больше напрягаться...

17
Для удовлетворения любопытства было бы интересно узнать, такой вариант где-нибудь воплощён?
Конкретно в таком виде - нет, наверное. Но был же Juice. Чтобы это воплотить, нужно работать в том же ключе - отложить часть компоновки исходника на момент загрузки приложения.

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

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

19
Ещё вариант - доопределять расширяемые типы на этапе компоновки. То есть, настоящие свои размеры тип узнает только после того, как ему предоставят реализацию интерфейса, который он импортировал.

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

21
Как, имея только интерфейс A, транслятору собрать модуль B с эффективным размещением локального массива s из элементов структур S неизвестного размера.
Ну, в общем, я дал ответ на этот вопрос. Делается исходник интерфейса, компилируется и получается символьный файл. Программист сможет получить из него интерфейс, а транслятор - полное описание всех структур. То есть, символьный файл - это и есть интерфейс.

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

Как я понимаю - проблема в наследовании, да? То есть, какой-то интерфейс использует для одного из своих типов в качестве базового тип из другого интерфейса. И необходимо, чтобы в расширенном типе присутствовали переменные, объявленные в базовом типе.

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

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

24
А такой вариант пробовали?

Выделяется довольно большой кольцевой буфер. Единым куском памяти.
Туда с начала и почти до конца (как придётся) пишутся данные из сети. В TCP/IP размер пакета может меняться, поэтому заранее предугадать или задать его нельзя. Или не желательно. Поэтому, очередной пакет пишется туда, где окончился предыдущий пакет. Когда в буфере заканчивается место или размер остатка будет делать работу сети не эффективной, то переходить к началу буфера.

Для обработки буфера будет использоваться два курсора. Первый отмечает позицию, следующую сразу за последней распознанной лексемой, а второй указывает на текущий обрабатываемый символ. У первого курсора два назначения: с одной стороны он не даёт перезаписать ещё не обработанные данные, а с другой - указывает, откуда следует начать повторный разбор лексемы, если потребуется.

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

Но я не понимаю, откуда восьмикратное увеличение производительности. По моим прикидкам, на каждые шеснадцать блокировок системной шины для связного списка должно происходить девять блокировок для массива. Это меньше двух раз. Некоторые потери на конвейере в случае со связным списком. Даже если взять с запасом, то пусть будет итоговое увеличение производительности в три раза. А откуда остальные разы?

26
В какой строчке происходит лишнее чтение блока?
Я бы сказал, что это чтение, которого можно избежать, а не "лишнее".first = first->next;Перед этой строчкой мы получаем адрес блока, в который будем писать, а в этой строчке читаем текущий "пустой" блок и достаём из него адрес следующего блока, чтобы положить его в условленное место - переменную first. Чтобы записать восемь блоков, нам потребуется прочитать восемь пустых блоков. Не совсем пустых, конечно: в них хранятся адреса. Но ничто не мешает хранить адреса сгруппированными отдельно (например, в другом блоке). Восемь сгруппированных адресов по восемь байт - это одна строка кэша. И одно вспомогательное чтение ОЗУ на восемь записей.

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

27
Значит, я правильно понял. И нюанс здесь в том, что сначала мы вычитываем пустой блок, заполняем его и записываем обратно. Хотя ничего не мешает просто записать данные по заранее известному адресу. Со связным списком сразу записывать не получится: по адресу, по которому мы хотим записывать, находится адрес очередного блока.

Я бы адреса свободных блоков хранил отдельно. Например, в стеке. Даже если потребуется этот стек хранить в тех же самых блоках.

28
Если распределяется 128 для 64, то можно адрес на вторую часть от 128 поместить в список свободных 64 таким образом, чтобы эти вторые 64 были отданы первым же запросом на размещение объекта. Минус подхода - усложнение проверки при возврате куска в пул свободных кусков. Если возвращаем первую половинку, то нужно проверить, что вторая половинка не занята, забрать из пула размещённый адрес на вторую половинку и вернуть блок. При возврате второй половинки проверить, не обнулена ли первая, и если обнулена, то вернуть блок.

Цитировать
Списки организуются на памяти самих же свободных объектов.
Если я правильно понял, и имелось в виду, что из 64 байт 8 байт выделяется под адрес на следующий объект, в котором заняты тоже только 8 байт для адреса, то идея не очень, по-моему.

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

30
В общем, если по полной воспользоваться советом Дизера, то можно получить следующее:

1. Выделять память блоками, кратными 2Q. Например, блок будет содержать 64 элемента 2Q.
2. Завести массив указателей на блоки (из пункта 1) и битовую карту на ячейки массива (в случае 64 элементов будет достаточно одного 64-битного числа).
3. У блока есть структура. Нулевой элемент блока служебный. Остальные используются для выделения памяти.
4. Спецификация служебной части блока:
   4.1. первый элемент - адрес или индекс свободной ячейки в этом блоке.
   4.2. битовая карта занятых ячеек блока.
   4.3. адрес и/или индекс блока в массиве блоков (из пункта 2).
   4.4. адрес битовой карты массива блоков.
   4.5. тип данных (размер Q или 2Q)

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

Допустим, у нас уже есть блок (из пункта 1), и мы хотим выделить память. Назовём этот блок активным. Выделение памяти происходит только в активном блоке. Одновременно активным для данного типа данных может быть только один блок. Мы смотрим в самое начало блока (без какого-либо смещения) и узнаём позицию свободной ячейки. Забираем эту позицию себе и записываем в начало блока увеличенный адрес. Размер блока фиксирован, поэтому момент заполнения блока проследить легко. Что если блок закончился? Когда выделять новый блок: сейчас или отложить до момента, когда придёт запрос на выделение нового объекта? Я бы отложил выделение нового блока, потому что могут оказаться полезными две оптимизации. Одна над текущим активным блоком, а вторая - концепция полупустых блоков. Также, вместе с увеличением адреса свободного блока установим и битик в нашей карте. Карта и адрес свободного блока находятся в одной кеш-линии, поэтому двукратного обращения к ОЗУ для получения двух этих значений быть не должно.

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

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

Вместо массива из пункта 2 можно просто побить большой кусок памяти на блоки и считать всё это массивом блоков. Или вместо списка блоков использовать стек адресов на свободные блоки.

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

Страницы: 1 [2] 3 4 ... 34