Вопрос инженера начального уровня относительно управления памятью

9

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

На простой вопрос, который я задал коллеге по работе, ответили: «Я сосредоточен на неправильных вещах». Хотя я уважаю этого сотрудника, я не согласен с тем, что это "неправильная вещь", на которой следует сосредоточиться.

Здесь был код (в VB), а затем вопрос.

Примечание. Функция GenerateAlert () возвращает целое число.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

против ...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

Первоначально я написал последнее и переписал его с «Dim alertID», чтобы кому-то еще было легче его читать. Но здесь была моя проблема и вопрос:

Если написать это с помощью Dim AlertID, на самом деле это займет больше памяти; конечно, но больше, и должен ли этот метод вызываться много раз, это может привести к проблеме? Как .NET будет обрабатывать этот объект AlertID. За пределами .NET следует утилизировать объект вручную после использования (ближе к концу подпрограммы).

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

Шон Хоббс
источник
1
Я мог бы легко доказать, что он на 100%, так как первая версия более читабельна. Держу пари, что компилятор может даже позаботиться о том, что вас беспокоит. Даже если это не так, вы преждевременно оптимизируете.
Rig
6
Я совсем не уверен, что он действительно использовал бы больше памяти с анонимным целым числом по сравнению с именованным целым числом. В любом случае, это действительно преждевременная оптимизация. Если вам нужно беспокоиться об эффективности на этом уровне (я почти уверен, что нет), вам может понадобиться C ++ вместо C #. Хорошо понимать проблемы с производительностью и то, что происходит под капотом, но это маленькое дерево в большом лесу.
PSR
5
Именованное целое и анонимное целое число не будет использовать больше памяти, тем более что анонимное целое - это просто именованное целое число, которое ВЫ не назвали (компилятор все еще должен назвать его). Самое большее, указанное целое число будет иметь другую область видимости, поэтому оно может жить дольше. Целое анонимное число будет существовать только до тех пор, пока метод нуждается в нем, а именованное будет жить столько, сколько нужно его родителю.
Джоэл Этертон
Посмотрим ... Если Integer является классом, он будет размещен в куче. Локальная переменная (скорее всего, в стеке) будет содержать ссылку на нее. Ссылка будет передана объекту errorDictionary. Если среда выполнения выполняет подсчет ссылок (или что-то подобное), то, когда ссылок больше нет, он (объект) будет освобожден из кучи. Все, что находится в стеке, автоматически «освобождается» после выхода из метода. Если это примитив, он (скорее всего) окажется в стеке.
Пол
Ваш коллега был прав: вопрос, поднятый вашим вопросом, должен был касаться не оптимизации, а читабельности .
Хайлем

Ответы:

25

«Преждевременная оптимизация - корень всего зла (или, по крайней мере, большей его части) в программировании». - Дональд Кнут

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

Стоит ли копаться в этих темах производительности и оптимизации? Абсолютно, но не на доллар вашей компании, если это не нужно.

Кристофер Берман
источник
1
На чём еще должен быть доллар? Ваш работодатель выигрывает от повышения ваших навыков гораздо больше, чем вы.
Марчин
Субъекты, которые непосредственно не влияют на вашу текущую задачу? Вы должны заниматься этими вещами в свое свободное время. Если бы я сел и исследовал каждый предмет CompSci, который пробудил мое любопытство в течение дня, я бы ничего не сделал. Вот для чего мои вечера.
Кристофер Берман
2
Weird. У некоторых из нас есть личная жизнь, и, как я уже сказал, работодатель выигрывает в основном от исследований. Ключ в том, чтобы не тратить на это весь день.
Марчин
6
Повезло тебе. Это не делает его универсальным правилом. Кроме того, если вы отговариваете своих работников от обучения на работе, все, что вы сделали, - отговорили их от обучения и предложили им найти другого работодателя, который фактически платит за повышение квалификации персонала.
Марчин
2
Я понимаю мнения, отмеченные в приведенных выше комментариях; Я хотел бы отметить, что я спросил во время моего обеденного перерыва. :). Еще раз, спасибо всем за ваш вклад здесь и на всем сайте Stack Exchange; это бесценно!
Шон Хоббс
5

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

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

jfrankcarr
источник
5

Как всегда говорил мой коллега:

  1. Сделай так, чтоб это работало
  2. Исправьте все ошибки, чтобы он работал безупречно
  3. Сделать это твердым
  4. Применить оптимизацию, если она работает медленно

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

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

Юсубы
источник
3

Стоит ли писать это с помощью Dim AlertID

Читаемость важна. В вашем примере , хотя, я не уверен , что вы на самом деле делать вещи , которые более читаемыми. У GenerateAlert () хорошее имя, и он не добавляет много шума. Вероятно, есть лучшее использование вашего времени.

на самом деле это заняло бы больше памяти;

Я подозреваю, что это не так. Это относительно простая оптимизация для компилятора.

должен ли этот метод вызываться много раз, может ли это привести к проблеме?

Использование локальной переменной в качестве посредника не влияет на сборщик мусора. Если функция GenerateAlert () new работает с памятью, это будет иметь значение. Но это будет иметь значение независимо от локальной переменной или нет.

Как .NET будет обрабатывать этот объект AlertID.

AlertID не является объектом. Результатом GenerateAlert () является объект. AlertID - это переменная, которая, если это локальная переменная, является просто пространством, связанным с методом для отслеживания вещей.

За пределами .NET следует вручную утилизировать объект после использования

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

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

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

Telastyn
источник
2

Заставь его работать, сделай его чистым, сделай его твердым, ТОГДА заставь его работать так быстро, как нужно .

Это должно быть нормальным порядком вещей. Ваш самый главный приоритет - сделать что-то, что пройдет приемочные испытания, которые не соответствуют требованиям. Это ваш приоритет, потому что это приоритет вашего клиента; выполнение функциональных требований в сроки разработки. Следующим приоритетом является написание чистого, удобочитаемого кода, который легко понять и который может быть поддержан вашим потомством без каких-либо WTF, когда это станет необходимым (это почти никогда не вопрос «если»; вам или кому-то после вас БУДЕТ идти вернуться и изменить / исправить что-то). Третий приоритет - сделать так, чтобы код придерживался методологии SOLID (или GRASP, если вы предпочитаете), который помещает код в модульные, многоразовые, заменяемые блоки, которые снова помогают в обслуживании (они не только могут понять, что вы сделали и почему, но есть чистые линии, по которым я могу хирургически удалить и заменить фрагменты кода). Последний приоритет - производительность; если код достаточно важен, чтобы он соответствовал спецификациям производительности, он почти наверняка достаточно важен, чтобы его сначала сделали правильным, чистым и твердым.

Повторяя Кристофера (и Дональда Кнута), «преждевременная оптимизация - корень всего зла». Кроме того, тип оптимизации, который вы рассматриваете, является второстепенным (ссылка на ваш новый объект будет создаваться в стеке, независимо от того, даете ли вы ему имя в исходном коде или нет) и типом, который может не вызывать каких-либо различий в скомпилированном. IL. Имена переменных не переносятся в IL, поэтому, так как вы объявляете переменную непосредственно перед ее первым (и, вероятно, только) использованием, я бы поспорил с пивом, что IL идентичен между вашими двумя примерами. Итак, ваш сотрудник на 100% прав; вы ищете не в том месте, если вы ищете именованную переменную против встроенной инстанции для чего-то для оптимизации.

Микро-оптимизации в .NET почти никогда не стоят (я говорю о 99,99% случаев). В C / C ++, возможно, если вы знаете, что делаете. Работая в среде .NET, вы уже достаточно далеко от металлического оборудования, что приводит к значительным накладным расходам при выполнении кода. Итак, учитывая, что вы уже находитесь в среде, которая указывает на то, что вы разочаровались в невероятной скорости, и вместо этого хотите написать «правильный» код, если что-то в среде .NET действительно не работает достаточно быстро, либо его сложность слишком высоко, или вы должны подумать о его распараллеливании. Вот несколько основных указателей для оптимизации; Я гарантирую, что ваша производительность в оптимизации (скорость, полученная за потраченное время) резко возрастет:

  • Изменение формы функции имеет большее значение, чем изменение коэффициентов - сложность WRT Big-Oh, вы можете уменьшить количество шагов, которые должны быть выполнены в алгоритме N 2 , вдвое, и у вас все еще есть алгоритм квадратичной сложности, даже если он выполняется в половину времени раньше. Если это нижняя граница сложности для этого типа проблемы, пусть будет так, но если есть решение NlogN, линейное или логарифмическое для той же проблемы, вы выиграете больше, переключая алгоритмы для уменьшения сложности, чем оптимизируя тот, который у вас есть.
  • То, что вы не видите сложность, не означает, что она вам не стоит - многие из самых элегантных однострочных слов работают ужасно (например, простое средство проверки регулярных выражений является функцией экспоненциальной сложности, но при этом эффективно простая оценка, включающая деление числа на все простые числа, меньшие, чем его квадратный корень, имеет порядок O (Nlog (sqrt (N))). Linq - отличная библиотека, потому что она упрощает код, но в отличие от механизма SQL, .Net Компилятор не будет пытаться найти наиболее эффективный способ выполнения вашего запроса. Вы должны знать, что произойдет, когда вы используете метод, и, следовательно, почему метод может быть быстрее, если поместить его в цепочку раньше (или позже), производя при этом те же результаты.
  • OTOH, почти всегда есть компромисс между сложностью исходного кода и сложностью во время выполнения - SelectionSort очень прост в реализации; Вы могли бы вероятно сделать это в 10LOC или меньше. MergeSort немного сложнее, Quicksort еще сложнее, а RadixSort еще сложнее. Но по мере того, как алгоритмы увеличивают сложность кодирования (и, таким образом, "предварительное" время разработки), они уменьшают сложность времени выполнения; MergeSort и QuickSort - это NlogN, а RadixSort обычно считается линейным (технически это NlogM, где M - наибольшее число в N).
  • Прорыв быстро - если есть проверка, которая может быть сделана недорого, что, вероятно, будет правдой и означает, что вы можете двигаться дальше, сделайте эту проверку в первую очередь. Если ваш алгоритм, например, заботится только о числах, заканчивающихся на 1, 2 или 3, наиболее вероятным случаем (учитывая совершенно случайные данные) является число, которое заканчивается какой-то другой цифрой, поэтому проверьте, чтобы число НЕ заканчивалось на 1, 2 или 3, прежде чем делать какие-либо проверки, чтобы увидеть, заканчивается ли число на 1, 2 или 3. Если часть логики требует A & B, и P (A) = 0,9, а P (B) = 0,1, то проверьте B сначала, если только если! A, то! B (как if(myObject != null && myObject.someProperty == 1)) или B занимает более чем в 9 раз больше, чем A для оценки ( if(myObject != null && some10SecondMethodReturningBool())).
  • Не задавайте вопросов, на которые вы уже знаете ответ - если у вас есть ряд «провальных» условий, и одно или несколько из этих условий зависят от более простого условия, которое также необходимо проверить, никогда не проверяйте оба это независимо. Например, если у вас есть проверка, которая требует A, и проверка, которая требует A && B, вы должны проверить A, и если true, вы должны проверить B. Если! A, то! A && B, так что даже не беспокойтесь.
  • Чем больше раз вы что-то делаете, тем больше вы должны обращать внимание на то, как это делается - это общая тема в разработке, на многих уровнях; в общем смысле разработки: «если обычная задача отнимает много времени или сложна, продолжайте ее выполнять, пока вы не разочарованы и не достаточно осведомлены, чтобы придумать лучший способ». В терминах кода, чем больше раз запускается неэффективный алгоритм, тем больше вы выигрываете в общей производительности, оптимизируя его. Существуют инструменты профилирования, которые могут взять двоичную сборку и ее символы отладки и показать вам, после прохождения некоторых сценариев использования, какие строки кода выполнялись чаще всего. Этим линиям и линиям, которые управляют этими линиями, следует уделять больше всего внимания, потому что любое увеличение эффективности, которое вы достигнете, будет умножено.
  • Более сложный алгоритм выглядит как менее сложный алгоритм, если на него бросить достаточно оборудования . В некоторых случаях вам просто нужно осознать, что ваш алгоритм приближается к техническим пределам системы (или ее части), на которой вы ее запускаете; с этого момента, если он должен быть быстрее, вы получите больше, просто запустив его на более качественном оборудовании. Это также относится к распараллеливанию; алгоритм N 2 -сложности при работе на N ядрах выглядит линейным. Итак, если вы уверены, что достигли нижней границы сложности для типа алгоритма, который пишете, ищите способы «разделяй и властвуй».
  • Это быстро, когда это достаточно быстро - Если вы не собираете упаковку вручную, чтобы ориентироваться на конкретный чип, всегда есть что-то, что можно получить. Однако, если вы НЕ ХОТИТЕ собирать упаковку вручную, вы всегда должны помнить о том, что клиент назвал бы «достаточно хорошим». Опять же, «преждевременная оптимизация - корень всего зла»; когда ваш клиент звонит достаточно быстро, вы закончите, пока он не будет думать, что это достаточно быстро.
Keiths
источник
0

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

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

Лорен Печтель
источник
0

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

Slapout
источник