Страницы

Поиск по вопросам

среда, 18 декабря 2019 г.

Какой тип переменной надо использовать в счетчике цикла и объявлении массива?

#типы_данных #любой_язык


Попробую пояснить.

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

Каждый тип данных занимает сколько-то места в памяти в байтах.

Примеры буду писать на C#

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

for (int i = 0; i < someVar.Length; i++) { 
// do something
}


Почему счетчик типа int? Почему не sbyte или byte?
Да, может она отработает быстро в данной области видимости. Но таких блоков много
же по коду. 

Аналогично с массивами. 
Зная точно длину (Поправочка. Т.к. ответы уже есть, а вопрос был задан...немного
сбил с толку... : не длину, кончено же, а точно зная максимальные числа.. к примеру
что будут числа  от 1 до 100 ). Все равно зачастую все объявляют

int[] arr = new int[8];


вместо

ushort[] arr = new ushort[8];


или

byte[] arr = new byte[8];


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

Так отсюда вопрос. Почему так все делают? Я чего-то не знаю еще пока или это просто
лень программисткая?
    


Ответы

Ответ 1



Ваше основное предположение — экономия памяти любой ценой — неверно. Например, процессор намного более эффективно работает с типом данных int, чем с byte, поэтому для ускорения работы программисты и компилятор используют int где только возможно, пренебрегая мелкими потерями в памяти и выигрывая в производительности. Точно так же обычно используется тип данных double вместо float потому, что он работает быстрее, хоть и занимает вдвое больше места. Затем, выравнивание структур данных. Компиляторы вставляют дополнительные байты между полями структур данных для выравнивания и тем самым более быстрого доступа. Ещё пример — loop unrolling и function inlining. Оптимизирующий компилятор разворачивает цикл for (int i = 0; i < 10; i++) f(i); в f(0); f(1); f(2); f(3); f(4); f(5); f(6); f(7); f(8); f(9); потому что это быстрее. Выигрыш в три байта никому не нужен, выигрыш в три миллисекунды ощущает любой. Далее, по поводу цикла. В современном программировании код должен быть достаточно общим, чтобы не акцентировать низкоуровневые оптимизации, а делать акцент на семантику, смысл кода. Поэтому низкоуровневые детали стараются прятать где только возможно. С этой точки зрения та деталь, что данные массив имеет конкретно 10 элементов, и для его индексации можно было бы использовать 4 бита (экономия!), просто игнорируется. Кроме того. возможность индексации «узким» типом данных не проверяема компилятором, а значит, если завтра этот код получит на вход массив с бОльшим количеством элементов, код молча перестанет работать. Правильный современный подход к перебору элементов массива таков: foreach (var item in array) { // process item } Здесь мы абстрагируемся от размера массива (будет работать с любым размером), от конкретного способа перебора (мы не кодируем явно порядок перебора элементов), от самого массива (тот же код сработает и со списком), таким образом перекладываем заботу о низкоуровневым оптимизациях на компилятор, и делаем код более простым в поддержке. Не стоит делать простые, тривиальные вещи сложно. Оставьте мощь вашего мыслительного процесса для реально сложных задач и для алгоритмической оптимизации. Пусть за вас оптимизирует компилятор, поверьте, он умеет это лучше.

Ответ 2



Объявлять в этом цикле for (int i = 0; i < someVar.Length; i++) { // do something } переменную i, как имеющую тип, например, byte не имеет никакого смысла, так как в данном условии i < someVar.Length она будет преобразована к типу int. То есть при генерации объектного кода будут добавлены дополнительные команды. Более того эта переменная локальная и большого влияние на использование памяти не оказывает. Что касается массивов, то обычно объявляют массив того типа, объекты которого требуются. Не всегда заранее можно сказать, какое будет верхнее и нижнее значения элементов массива. Если же вы точно знаете, что вас устраивает массив типа byte, то вы можете определить массив такого типа. Однако, опять-таки, в различных арифметических и других операциях объекты вашего массива будут постоянно преобразовываться к типу int. Есть и другие связанные с этим проблемы. Например, в C++ вы можете объявить массив, имеющий тип int8_t. Однако этот тип является алиасом для типа char. Тогда при выводе элементов такого массива на консоль, используя оператор operator << у вас возникнут трудности, так как этот оператор будет пытаться выводить целые значения как символы. Например, Если вы объявили массив int8_t a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; то при выводе его на консоль for ( int8_t x : a ) std::cout << x; на экране консоли появятся странные знаки, или же при выводе числа 9 вообще ничего не появится, а курсор перескочит на несколько позиций вперед, так как значение 9 будет рассматриваться оператором вывода как символ табуляции. Поэтому вам придется постоянно помнить, что вместо вышеуказанного предложения вам надо писать for ( int x : a ) std::cout << x; ^^^^ Это все лишь может служить источником трудно находимых ошибок. Вот еще один пример на C++, который может привести к ошибке. Допустим у вас есть следующие объявления unsigned short a = 5; unsigned short b = 10; std::cout << a - b << std::endl; unsigned int x = 5; unsigned int y = 10; std::cout << x - y << std::endl; Как видите, все переменные имеют беззнаковый целочисленный тип и одни и те же значения. Спрашивается, будет ли вывод на консоль у двух операторов вывода std::cout << a - b << std::endl; std::cout << x - y << std::endl; одинаков?:) Проверьте это сами.:) Есть дилемма: больше памяти - больше скорость выполнения, меньше памяти - меньше скорость выполнения. Вы можете оптимизировать свою программу, исходя из тех критериев, которые для вас приоритетны. Но заниматься оптимизацией надо тогда, когда вы точно знаете причину и место неудовлетворительной работы программы.

Ответ 3



Про оптимизацию на спичках. В выражении for (int i = 0; i < someVar.Length; i++) { ... } происходит единичное выделение памяти и единичное освобождение после выхода. Даже если таких блоков много, каждый раз вы получите единичное выделение и освобождение. Дело меняется, только если у Вас есть вложенные циклы, но и данном случае речь идет о единичных выделениях, которые сразу же освобождаются. В итоге ваша программа в любой момент времени на таких блоках, сколько бы их ни было, будет экономить в среднем 4-7 байта(12-21 в случае с двумя-тремя вложенными циклами) и терять кучу производительности. Ответьте себе на вопрос, с учетом вышесказанного, так ли важны для программы эти пару байтов? В массивах не путайте их длину и их хранимый тип данных. В длину в квадратных скобках передают int и она опять же является единичным выделением памяти, а тип массива определяют именно исходя из хранящихся в нем данных(Int16, Int32, Int64). Хотя если данных не много и памяти точно хватит, иногда можно и пренебречь поставив обычный Int32. В любом случае в Вашей программе наверняка найдется куча других мест, которые гораздо критичнее с точки зрения выделения памяти и скорости, чем выше приведенные.

Ответ 4



массив содержит значинеия типа int int[] arr = new int[8]; массив содержит значения uint16 ushort[] arr = new ushort[8]; массив содержит значения типа byte byte[] arr = new byte[8]; Вы превели некорректные примеры. И как правильно заметил @BOPOH размерность Length это int, а есть еще LongLength, который размера long или int64

Комментариев нет:

Отправить комментарий