OberonCore

Библиотека  Wiki  Форум  BlackBox  Компоненты  Проекты
Текущее время: Четверг, 28 Март, 2024 16:25

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 10 ] 
Автор Сообщение
 Заголовок сообщения: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 11 Август, 2016 09:23 

Зарегистрирован: Пятница, 13 Март, 2009 16:36
Сообщения: 987
Откуда: Казань
Память в программе выделяется при помощи оператора NEW, а вот что делать дальше, там возможны вариации:
1) Можно сразу начать использовать выделенную память. В случае, если памяти не хватило, и выделенная переменная была NIL, программа сразу же упадёт.
2) Можно проверить, что выделенная переменная не NIL. В случае, если она не NIL, то продолжить работу, возможно, дальше выделять еще динамические объекты, в случае же если NIL, то как можно быстрее закончить работу функции и вернуть результат работы функции, как FALSE.
Бывают ситуации, когда несколько динамических объектов взаимодействуют друг с другом, допустим, есть динамическая строка, где содержится многострочный текст, и динамический массив позиций начала каждой строки. Может быть такая ситуация, что сначала текст добавляется в динамическую строку, а затем вычисляются позиции начала каждой строки, но на этом этапе может возникнуть ситуация, что памяти может не хватить. И может получится так, что динамическая строка увеличивалась, а динамический массив позиций начала строк не увеличился. И система будет находится в несогласованном состоянии.
3) Можно реализовать у каждого динамического объекта функцию Revert, которая будет отменять последнюю выполненную операцию добавления данных. И в случае, если динамическая строка выделилась, а массив динамических строк не выделился, то вызвать операцию Revert для динамической строки и вернуть результат работы функции, как FALSE. В этом случае, если результат работы функции FALSE, то это будет означать, что ничего не добавилось и все данные остались в том же состоянии, что и до попытки добавить данные.

Как мне кажется, третий вариант наиболее продвинутый, но не будет ли такая реализация излишним усложнением? Кто какие подходы использует?

И как можно тестировать ошибки выделения памяти? Допустим, можно написать обёртку над NEW и, например, искусственно возвращать NIL, это можно легко сделать, если хочется всегда возвращать NIL. А вот как сделать так, чтобы память успешно выделялась на этапе выделения динамической строки, но возвращала NIL, на этапе выделения динамического массива с позициями начала каждой строки? Конечно, зная реализацию, можно подсчитать, что N-ый NEW должен вернуть NIL и завести счетчик внутри обертки, который будет считать сколько NEW было вызвано. Но в данном случае, такой тест становится очень чувствительным к любым изменениям в реализации. Допустим, если добавится, еще какой-нибудь вызов функции, который выделяет память, то все подсчеты могут съехать. Можно ли сделать такой тест более надежным?


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 11 Август, 2016 12:52 

Зарегистрирован: Пятница, 13 Март, 2009 16:36
Сообщения: 987
Откуда: Казань
По поводу тестирования с искусственным возвратом NIL, думаю, возможны два варианта:
1) Каким-нибудь способом узнавать тип указателя, который сейчас выделяет NEW. И если типы указателей разные, то можно не считать N-й NEW, который должен вернуть NIL, а поставить проверку на тип указателя, который должен вернуть NIL.
2) Если типы указателей одинаковые, то предыдущий способ не будет работать, но в этом случае, думаю, можно каким-нибудь способом получить стек вызовов и поставить проверку на то, что NEW вызывается из определенной функции.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 11 Август, 2016 15:37 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Можно попробовать не смешивать уровень языка (оператор NEW) и прикладной уровень (инициализация массива указателей).

Пусть на уровне языка NEW возвращает NIL, если не может выделить память. Тогда выделение массива указателей либо сработает, либо нет. Если не сработало, NEW вернёт NIL вместо массива. Если сработало, будет массив, заполненный NIL вместо указателей на строки.

Далее прикладной уровень в цикле может заполнять массив, вызывая NEW для каждого элемента. Как только очередной элемент не удалось создать (NEW вернул NIL), цикл можно преждевременно прерывать. Далее по обстоятельствам: либо зачистить уже выделенные элементы массива, либо удалить ссылку на сам массив, поскольку не удалось его полностью проинициализировать (по принципу "всё либо ничего").


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 11 Август, 2016 16:07 
Аватара пользователя

Зарегистрирован: Вторник, 19 Сентябрь, 2006 21:54
Сообщения: 2449
Откуда: Россия, Томск
Что касается тестирования, то надёжнее всего будет сделать интерфейс для имитации сбоев. Например, флажок завести какой-то, который будет приводить к облому выделения памяти. Никто не запрещает ориентироваться на тестирование и официально его поддерживать на уровне интерфейса модуля.

Это как пунктирные "линии отрыва" на бумажных билетах. Если не нужно отрывать, они не мешают, но когда нужно оторвать, будет разрыв по предусмотренной траектории. Так же и тестирование с заложенными в код возможными проблемами: IF (result = NIL) OR imitateOutOfMemory THEN ...

Или так:
Код:
IF imitateOutOfMemory THEN
    result = NIL
ELSE
    NEW(result)
END


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Пятница, 12 Август, 2016 14:59 

Зарегистрирован: Пятница, 13 Март, 2009 16:36
Сообщения: 987
Откуда: Казань
Александр Ильин писал(а):
Что касается тестирования, то надёжнее всего будет сделать интерфейс для имитации сбоев. Например, флажок завести какой-то, который будет приводить к облому выделения памяти. Никто не запрещает ориентироваться на тестирование и официально его поддерживать на уровне интерфейса модуля.

С интерфейсом идея интересная. Пока для себя решил сделать модуль UnitTest, который будет иметь функции IsCondition, SetCondition, ResetConditions.
В коде программы можно будет писать так:
Код:
NEW(pointer);
(* Первый параметр, имя модуля и функции, а второй параметр, порядковый номер NEW в функции. *)
IF UnitTest.IsCondition("ModuleName.FunctionName", 1) THEN
  pointer := NIL;
END;


Затем в самом тесте предварительно можно будет устанавливать условия, которые должны сработать:
Код:
UnitTest.SetCondition("ModuleName.FunctionName", 1);


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Пятница, 12 Август, 2016 15:24 

Зарегистрирован: Пятница, 13 Март, 2009 16:36
Сообщения: 987
Откуда: Казань
Можно обойтись без строкового параметра, а использовать только число, а числа все обозначить константами, например:
Код:
CONST
  ModuleFunction1 = 1;
Так проверку того, что условие установлено или не установлено, можно сделать гораздо быстрее.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Вторник, 08 Ноябрь, 2016 16:33 

Зарегистрирован: Четверг, 08 Май, 2008 19:13
Сообщения: 1447
Откуда: Киев
Если говорить про персональные компьютеры, к которым я бы отнёс и смартфоны, то, на мой взгляд, проверка выделения памяти для объектов, меньше ~10-100 Мегабайт, является бессмысленным усложнением. По опыту скажу, что при заканчивающейся физической памяти, аллокатор не возвращает NULL, а выводит ОС в состояние длительного обмена данными с накопителем. Не знаю, как на Windows, но на Mac OS X и GNU/Linux, при засбоившей программе, выделяющей память, компьютер вместо аварийного завершения программы, продолжает выделять память и перестаёт отвечать на запросы пользователя и единственный гарантированный способ привести его в чувство - это сброс. На iOS я выделял кусками около 8Gb памяти при наличии 1GB физически, после чего приложение падало на системных библиотеках вне зависимости от наличия проверок в вашем коде.
Более того, уже в документации malloc для GNU/Linux написано прекрасное. Оцените юмор:
Цитата:
By default, Linux follows an optimistic memory allocation strategy. This means that
when malloc() returns non-NULL there is no guarantee that the memory really is avail‐
able


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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 10 Ноябрь, 2016 11:45 

Зарегистрирован: Пятница, 13 Март, 2009 16:36
Сообщения: 987
Откуда: Казань
Comdiv писал(а):
Если говорить про персональные компьютеры, к которым я бы отнёс и смартфоны, то, на мой взгляд, проверка выделения памяти для объектов, меньше ~10-100 Мегабайт, является бессмысленным усложнением.

В принципе, в этом есть здравое зерно.
Запрограммировал небольшую математическую модель:
- допустим, дано 1 Гб свободной памяти
- программа равновероятно выделяет блоки памяти от 32 байт до 1 Гб, размерами равными степени двойки
- выделение происходит до тех пор, пока сумма размеров выделенной память меньше, либо равна 1 Гб
- данный тест повторяется миллион раз
- затем подсчитывается вероятность как отношение успешных попыток к сумме успешных и неуспешных.
Получились следующие результаты:
0;0.997588
1;0.99757
2;0.997554
3;0.997568
4;0.997529
5;0.997573
6;0.997563
7;0.99758
8;0.997581
9;0.997566
10;0.997571
11;0.99754
12;0.997509
13;0.997527
14;0.997461
15;0.997353
16;0.997057
17;0.996472
18;0.995243
19;0.992511
20;0.986395
21;0.972398
22;0.939715
23;0.860733
24;0.656508
25;0.0657939
Или если представить это на графике:
Вложение:
Probability.png
Probability.png [ 9.96 КБ | Просмотров: 7314 ]

В общем, вероятность успешного выделения блока размером 32 байта равна 99,7588%, а вероятность успешного выделения блока размером 1 Гб, если всего есть только 1 Гб, равна 6,57939%.
С другой стороны, нехватка памяти может произойти и при выделение самого малого блока и вероятность 0,24% не такая уж и маленькая. Получается, что из 400-х пользователей, у одного из них может возникнуть ошибка как раз с выделением самого маленького блока. Поэтому я считаю, что всегда необходимо проверять успешность выделения памяти.


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 10 Ноябрь, 2016 15:59 

Зарегистрирован: Четверг, 08 Май, 2008 19:13
Сообщения: 1447
Откуда: Киев
Rifat писал(а):
С другой стороны, нехватка памяти может произойти и при выделение самого малого блока и вероятность 0,24% не такая уж и маленькая. Получается, что из 400-х пользователей, у одного из них может возникнуть ошибка как раз с выделением самого маленького блока. Поэтому я считаю, что всегда необходимо проверять успешность выделения памяти.

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


Вернуться к началу
 Профиль  
 
 Заголовок сообщения: Re: Надёжная работа с памятью
СообщениеДобавлено: Четверг, 10 Ноябрь, 2016 16:24 

Зарегистрирован: Пятница, 13 Март, 2009 16:36
Сообщения: 987
Откуда: Казань
Comdiv писал(а):
Более того, уже в документации malloc для GNU/Linux написано прекрасное. Оцените юмор:
Цитата:
By default, Linux follows an optimistic memory allocation strategy. This means that
when malloc() returns non-NULL there is no guarantee that the memory really is avail‐
able


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

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


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 10 ] 

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 6


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2005-2024, участники конференции «OberonCore», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Без разрешения участников и ссылки на конференцию «OberonCore» любое воспроизведение и/или копирование высказываний полностью и/или по частям запрещено.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB