Как сделать код максимально быстрым?
Функция clock имеет очень низкое разрешение, кажется 16ms. Это слишком грубо для вашей задачи.
Внутренний цикл лишний. Во внешнем цикле делите число на первый делитель, если остаток нулевой, вы нашли пару делителей.
Поиск делителей логично начинать от меньших к большим. Первый делитель ищите в диапазоне [1, sqrt(N)] .
Найдя один делитель можно искать следующие пары делителей не для исходного числа, а для числа уже разделённого. Это сильно ускорит процесс, хотя пары делителей для оригинального числа надо будет восстанавливать.
Обычно делители не ищут парами. Число разлагают на простые: факторизация целых чисел. Затем из простых можно сконструировать все нужные пары. Это самый быстрый способ.
Примеры
Оригинальная программа videx. Работает медленно, так как проверяет все пары чисел до N (число, которое факторизуем). Сложность O(N^2) . Почти все пары печатаются два раза. Если N > 46340 (= sqrt(2^31)) из-за переполнения ошибается.
Предложена user419509. Делит N на все числа до sqrt(N) . Сложность O(sqrt(N)) . N < 2^31 .
Предложена Harry. Делит N на все нечетные числа до sqrt(N) . Если делитель найден, то сокращает на него N и продолжает поиск. Сложность в худшем случае O(sqrt(N)) . N < 2^32 . Разложения на множители делаются из факторизации отдельным проходом.
Содержит таблицу простых чисел до 2^16 . Делит N на все простые числа до sqrt(N) . N < 2^32 .
Делает wheel factorization для простых 2, 3, 5, 7, 11, 13 . Делит N на все числа из "колеса" до sqrt(N) . N < 2^64 .
В таблице приведены худшие времена работы, когда N разлагается в произведение двух примерно равных простых чисел:
Предельная производительность: C#
Я поделюсь 30 практиками для достижения максимальной производительности приложений, которые этого требуют. Затем, я расскажу, как применил их для коммерческого продукта и добился небывалых результатов!
Приложение было написано на C# для платформы Windows, работающее с Microsoft SQL Server. Никаких профайлеров – содержание основывается на понимании работы различных технологий, поэтому многие топики пригодятся для других платформ и языков программирования.
Предисловие
Всё началось в далёком 2008 году – тогда я начал заниматься задачами сравнения и репликации реляционных баз данных. Такого рода приложения в первую очередь требуют высокого качества исполнения, т.к. потеря или порча данных может обернуться катастрофой. Во-вторых, размеры баз могут достигать сотни и тысячи гигабайт, поэтому от приложения требуется высокая производительность.
Основной упор делался на SQL Server, а так как Microsoft давно отказались от нормальной поддержки ODBC драйвера и предоставляют полный функционал только в ADO .NET provider, то приложение необходимо было писать на одном из языков для .NET Framework. Конечно сочетание High Performance Computing и платформа .NET как минимум вызывает улыбку, но не спешите с выводами.
Когда продукт был готов, результаты превзошли все ожидания, и производительность в разы превосходила лучшие решения на рынке. В данной статье я хочу поделиться с вами основными принципами, которых стоит придерживаться при написании высокопроизводительных приложений.
Принципы оптимизации
Все принципы сгруппированы в категории, а их описание может содержать утверждения без объяснения, однако все они основаны на фактах и исследованиях, которые можно легко найти в интернете.
Каждый из пунктов содержит краткую информацию, которая отображает основную суть. Некоторые детали могут быть не освещены из-за насыщенности материала, а по каждому из пунктов можно писать отдельную статью. Поэтому я настоятельно рекомендую ознакомиться с деталями каждого пункта самостоятельно из любых доступных источников, перед применением их на практике. Неверное понимание изложенного материала и пробелы в знаниях могут привести как минимум к ухудшению производительности вашего приложения.
Все примера кода являются тривиальными, и демонстрируют исключительно основную суть, а не решают конкретную задачу.
1. Хороший алгоритм.
В первую очередь, для проблемы необходимо подобрать наилучший алгоритм её решения. Только после этого можно заниматься оптимизацией. Если алгоритм выбран неправильно, никакая оптимизация не поможет.
Углублённые познания работы различных технологий – основа всех приёмов оптимизаций. И эти знания помогают в выборе алгоритма из нескольких возможных.
2. План действий.
Если вы чётко сформируете все задачи, который выполняет алгоритм, во-первых, вам будет легче понять и реализовать алгоритм, во-вторых вы сможете построить граф действий – так вы узнаете чёткий порядок их выполнения и какие действия можно выполнять параллельно.
3. Микро-задержки.
Есть некая операция, время выполнения которой занимает F секунд. Если уменьшить это время всего на 1 микросекунду, то за 100 миллионов итераций вы получите выигрыш в 100 секунд! Такие частые операции – первый кандидат для оптимизации.
Ветвление кода
4. Не используйте «if», когда всё заранее известно.
Таким образом, мы проверяем типы данных в полях только один раз при инициализации, а не на каждой записи, что очень положительно отражается на производительности. К тому же, если какой-то тип данных не поддерживается вашим кодом, то вы получите ошибку сразу, а не после чтения миллионной записи. Думаю, про вредность Boxing / Unboxing в этом примере не стоит рассказывать, и надеюсь, вы догадываетесь как это исправить.
5. Вторая причина не использовать «if».
Любой современный процессор – это не «набор транзисторов», который тупо выполняют указанные команды. Процессор – это очень сложное технологическое устройство, в котором есть свои трюки для оптимизации выполнения инструкций.
Instruction Scheduling – один из видов оптимизации, который позволяет параллельно выполнять инструкции. Представьте код:
Инструкции, вроде как идут последовательно, но данные между первым и вторым выражением никак не связаны между собой. Это значит, что ещё на этапе компиляции можно задействовать все регистры общего назначения, при этом процессор сможет выполнить операции с этими регистрами практически параллельно. А вот третье и четвёртое выражения уже имеют зависимые данные, и тут оптимизировать не получится.
Польза от параллельности очевидна, и сейчас я объясню как это связано с «if». Дело в том, что когда процессор встречает условный переход, то он заранее не может знать выполнится ли условие или нет, поэтому об эффективности оптимизаций с выполнением инструкций параллельно можно забыть, не смотря на то, что есть такие техники как Branch prediction.
Вот вам задачка: как сравнить два байта без использования оператора «if»? Метод должен возвращать -1, 0, или 1, а его сигнатура: int CompareBytes(byte a, byte b)
Сам по себе этот подход работает быстрее, чем его аналог с двумя «if». Но для данного примера компилятор лучше оптимизирует программу в целом, если будет использоваться «if» с прямым вызовом метода (без делегатов и без виртуализации). Тем не менее, такого рода подход в определённых местах может сократить время выполнения программы.
6. Эффективные циклы.
- Никаких «foreach» – эта конструкция может создать экземпляр класса, который реализует интерфейс IEnumerable (чем плохо, описано в принципах 16 и 23).
- Никогда не объявляйте переменные типами IList или IEnumerable, если вы знаете точный тип коллекции (см. принцип 24). Можно воспользоваться «var».
- По возможности, используйте встроенные массивы, а не класс List. Это связано с проверкой границ массива в индексаторе, которые сказываются на производительности. Единственный способ избежать этих проверок, написать «for» таким образом:
- Обращайтесь к элементу массива только один раз – больше шансов, что значение будет помещено только в регистр общего назначения (без лишних обращений к памяти), а также избежите лишних проверок границ массива (если таковые имеют место).
7. Разматывайте циклы.
Этот метод может привести к ухудшению производительности в некоторых случаях, поэтому требует особой аккуратности. Советую почитать для начала «Loop unwinding» на Wikipedia.
Асинхронные задачи
8. Примитивы синхронизации.
В главном потоке программы можно создать таймер, который периодически опрашивает прогресс выполнения задачи и отображает его в UI. Если бы у переменной «progress» не было модификатора «volatile», то была бы вероятность, что актуальное её значение в итерациях цикла будет храниться только в регистре процессора и для главного потока она будет всегда 0. Такой подход к чтению прогресса избавляет от надобности синхронизации потоков, которая замедляет выполнение асинхронной задачи, и тем более от ужасной методики рассылки событий на каждое изменения значения прогресса.
9. Задачи и ресурсы.
- первая считает общее количество бит в большом массиве данных в памяти
- вторая читает данные из сетевого протокола, и пишет на диск
- а третья шифрует небольшое значение с помощью AES-256
10. Пакетная обработка.
11. Переключение контекста.
Вы знаете, почему потоки могут работать «параллельно» даже, если у процессора всего одно ядро? Каждая программа имеет изолированную от других память (виртуальная память), но все они используют одни и те же регистры процессора для выполнения инструкций. Чтобы дать возможность использования регистров процессора «параллельно», придумали технику переключения контекста – сохранение состояния регистров в оперативную память при остановке выполнения программы, чтобы можно было их позже восстановить для продолжения выполнения. Т.е. система выполняет часть инструкций одной программы, сохраняет её состояние, потом загружает состояние другой программы, выполняет немного её инструкций, потом опять сохраняет состояние, и так далее. Таким образом, система много раз в секунду переключается с программы на программу, что даёт эффект «параллельности выполнения».
Переключение контекста – довольно тяжеловесная операция, поэтому слишком большое кол-во потоков ведёт к деградации производительности. Чем меньше приходится потоков на одно физическое ядро процессора, тем меньше происходит переключений контекста, тем быстрее работает система в целом.
Во избежание переключения контекста, можно привязать потоки к конкретным физическим ядрам/процессорам. Для процессов есть свойство Process.ProcessorAffinity (общая маска для всех потоков процесса), а для потоков можно использовать ProcessThread.ProcessorAffinity.
Для контроля выделения времени выполнения потоков есть приоритет – свойство Thread.Priority. Чем больше приоритет, тем больше выделяется процессорного времени на выполнение инструкций. Также есть приоритет у процессов в системе – Process.PriorityClass.
Дисковое хранилище и сети
12. Выравнивание по размеру кластера.
В сетевых протоколах передачи данных используется термин MTU (Maximum Transmission Unit) для определения размера блока данных для передачи. Но ситуация обстоит несколько сложней и остаётся для самостоятельного изучения.
13. Асинхронная запись.
Кто же не писал код, в котором в Stream последовательно записываются байты… Что в этом плохого? Давайте рассмотрим, что происходит внутри. Если помнить, что FileStream использует внутреннюю буферизацию, то всё хорошо, пока буфер заполняется. Как только он заполнился, всё его содержимое скидывается на диск. Но кто сказал, что эта операция мгновенна? (особенно если это HDD на вэб-сервере.) Поэтому, поток программы останавливается, и ждёт завершения записи. И так каждый раз когда, когда буфер полностью заполняется.
Для устранения этих задержек достаточно создать свой класс Stream, который накапливает данные в буфер, а потом ставит его в очередь на запись в отдельном потоке. Пока первый буфер стоит в очереди и собственно записывается, можно уже наполнять второй буфер (см. принцип №9). Таким образом, ожидание окончания записи заменяется задержкой синхронизации потоков. И только при закрытии Stream остаётся дождаться окончания записи в другом потоке – это необходимо для обработки ошибок. А для конечного пользователя это будет тот же прозрачный интерфейс класса Stream. По-моему, отличный выигрыш! Только не стоит забывать, что это работает только для больших файлов. Если данные занимают пару кластеров, этот подход не имеет смысла.
Также подумайте о более существенных задержках, которые возникают при передаче данных по сети (например, с использованием NetworkStream).
14. Чтение наперёд.
Как и при записи (принцип №13), чтение кластера с диска занимает время. Если стоит задача прочитать большой файл последовательно от начала до конца, то можно аналогично организовать асинхронное чтение файла наперёд во внутренние буферы. Пока один из буферов используется, в следующий происходит чтение соседнего кластера. При этом так же сохраняется прозрачность интерфейса Stream.
И не забываем о насущности проблемы при чтении данных из сетевого интерфейса.
15. Сжатие данных.
- Необходимо записать/прочитать меньше байт. Линейное уравнение отношения объёма к скорости даёт меньшее время.
- Меньше данных = меньше кластеров = меньше IO операций
Управление памятью
16. Структуры, а не классы.
- Структуры занимают меньше места в памяти, т.к. у них нет заголовка описывающий тип данных, указателей на таблицы виртуальных методов, а так же другие поля, необходимые для синхронизации и сборки мусора.
- Структуры хранятся в stack’е (но в куче, если массив). Выделение памяти в stack’е происходит очень быстро: stack – заранее выделенный буфер памяти, в котором просто резервируется место по размеру структуры (в основном, на этапе компиляции) путём уменьшения значения в stack pointer (уменьшения, т.к. данные в stack’е хранятся задом-наперёд). Когда функция завершает свою работу, то «освобождение» всех переменных в stack’е происходит один махом путём увеличения указателя stack pointer на количество байт, необходимых для переменных. А выделение и освобождение памяти в куче – это огромное количество операций, в отличие от простого вычитания и суммирования.
- Из-за того, что структуры хранятся в stack’е, они не требуют сборки мусора. Это сильно разгружает сборщик мусора и избавляет от проблемы фрагментации памяти.
- Структуры, поля которых – только value types, легко сериализовать в массив байт и обратно.
17. Освобождение памяти.
Не смотря на автоматическое освобождение памяти в .NET Framework, не стоит забывать, как работает сборка мусора. Все объекты представляются как вершины графа, а ссылки на объекты – как его рёбра. Для простоты (хоть это не так), самой первой вершиной графа можно считать класс, который определяет точку входу в приложение (с функцией main). Как только некие рёбра графа всех объектов будут убраны так, что образуется два несвязанных графа, то объекты того, который не связан с точкой входа, можно удалить из памяти.
Поскольку рёбра графа представляются ссылками, то удаление рёбер можно считать установку ссылки в значение «null». Если вы забываете это делать, ваши объекты будут переживать сборку мусора и переходить из поколения в поколение, увеличивая общий расход памяти приложением.
Здесь можно вывести для себя очень простое правило – как только объект перестаёт быть нужным, обнулите ссылку на него.
18. Повторное использование памяти.
На уровень ниже
19. «unsafe» и указатели.
Код с пометкой «unsafe» позволяет в C# использовать указатели, и это открывает широкие возможности для оптимизации. Вы можете интерпретировать массив байт как угодно – как массив int или структур Guid, вы забываете про проверки границ массива, вы можете выравнивать адреса в памяти, и т.п. Без использования unsafe кода никак не получится максимально оптимизировать приложение, которое интенсивно работает с большими объёмами данных в памяти.
20. Компактное хранение.
Очень часто структуры содержат данные, которые можно легко вычислить. Поэтому, стоит задуматься о балансе между вычислением значения поля «на лету» и использовании памяти для хранения значений таких полей в каждом элементе массива.
И, вдобавок, если много элементов массива имеют поле с абсолютно одинаковыми значениями, то можно сгруппировать такие элементы и хранить значение в группе, а не в поле каждого элемента.
21. Выравнивание в памяти.
Представьте себе структуру данных:
Общий её размер составляет 5 байт. Скажем, у вас есть массив S[], и некая функция в цикле что-то делает с полем «b». Предположим, что оператор «new» выделил кусок памяти для массива, и указатель на первый элемент равен 0x100000 – здесь я просто хочу подчеркнуть, что адрес является степенью двойки, или, по крайней мере, кратен 4 или 8. Теперь для первого элемента в массиве адрес для поля «b» будет 0x100001, для второго – 0x100006, третьего – 0x10000B, и т.д. Некоторые архитектуры процессоров требуют, чтобы адреса для чтения или записи были кратны размеру машинного слова – это и называется «выровненный адрес». Для архитектур x86 и x86-64 (где размер машинного слова 4 и 8 байт соответственно) таких требований нет, однако чтение и запись по выровненным адресам дают лучшую производительность.
Для выравнивания структур данных используется метод padding – добавление лишних байт, чтобы все размеры всех полей структуры были кратны указанному числу байт. Если выровнять нашу структуру на 4 байта, то получится:
Чтобы не писать всё это руками, в .NET Framework есть StructLayoutAttribute. Имейте в виду, выравнивание может очень негативно обернуться для производительности – добавляя лишние байты, размер данных может сильно увеличиться, что приводит к большему количеству обращений к памяти.
Тут есть и ещё интересная сторона. Практически все знают, что у процессоров есть внутренний кэш, да и ещё нескольких уровней. Но далеко не все знают, как он работает. Вкратце, есть так называемые cache lines – небольшие блоки памяти (например, 32 байта), в которые записываются данные из оперативной памяти по выровненному адресу (на размер кэш линии). Если взять наш пример со структурой «S» размером 5 байт, то процедура, которая в цикле последовательно обращается к данным элементов массива, будет на самом деле читать первые 6 элементов из кэша процессора (при размере кэш-линии в 32 байта; я имею в виду, что процессор это будет делать, кэш-то у него). Для седьмого элемента первые два байта структуры хранятся в последних двух байтах кэш-линии, а остальные три байта структуры – в физической памяти. При чтении происходит кэш-промах, и тогда процессору приходится читать данные из физической памяти, что намного медленнее чтения из кэша. Из этого следует, чем больше будет обращений к кэшированной памяти, тем быстрее будет работать приложение. В вопросе «PInvoke for GetLogicalProcessorInformation» на StackOverflow вы можете взять код для получения размера кэш линий.
Вторая, не менее интересная сторона, заключается в понимании как происходит выделение физической памяти. Каждый процесс имеет свою виртуальную память, которая неким образом соотносится с реальной физической памятью. В распространённой модели постраничной фрагментации памяти, где каждая страница памяти обычно занимает 4 KiB, последовательная виртуальная память размером 8 KiB может ссылаться на две НЕпоследовательные страницы физической памяти. Помните «файл подкачки»? Как вы думаете, какими кусками Windows записывает туда и считывает оттуда память? Так что выравнивание адресов в памяти на размер страницы иногда может быть большим плюсом.
22. Преимущества x86-64.
Две операции вместо 11 – по-моему, очень хорошо!
23. Вызов функций.
- Безусловный переход по адресу в памяти.
- Запись и чтение адреса из stack’а для CALL/RET.
- Запись и чтение аргументов метода из stack’а.
- Задержкам, связанным с обращением к памяти. А операции с регистрами выполняются намного быстрее.
- Невозможности эффективно использовать регистры процессора при компиляции IL в родной код.
- Отсутствию возможности для процессора проанализировать инструкции наперёд и выполнить их параллельно (из-за JMP-подобных инструкций).
Если помнить, что структура Guid занимает 16 байт, то при вызове такого метода в stack будет записано 32 байта, чтобы передать аргументы g1 и g2. Вместо этого, вы можете написать:
И тогда, для каждого аргумента будет передан указатель на него, а не его значение, и это будет 4 байта для 32-разрадного процесса, и 8 байт для 64-разрадного процесса. Более того, появится вероятность, что адреса на аргументы будут переданы через регистры процессора, а не stack (в зависимости от соглашения вызова).
Пример 2. Тривиальный метод, который умножает два числа, и метод, который его вызывает:
Если бы метод Foo просто делал умножения вместо вызова метода Multiply, тогда бы значения a и b просто были помещены в регистры процессора и умножены. Вместо этого мы заставляем компилятор генерировать лишний код, который делает кучу операций с памятью, да и ещё безусловный переход! А если метод вызывается миллионы раз в секунду? Вспомните про принцип №3.
Поэтому в таких языках, как C++ есть модификатор метода inline, который указывает компилятору, что тело метода необходимо вставить в вызывающий метод. Правда, и в .NET Framework только с версии 4.5 наконец-то можно делать inline методы (в предыдущих версиях невозможно было форсировано делать inline — только на усмотрение компилтора). А если говорить о микроконтроллерах, код для которых пишется на assembler, то там зачастую вообще запрещены вызовы функций. Хочешь выполнить код функции – делай Copy Paste.
Только не спешите переписывать код вашей программы в одну большую функцию – это приведёт к раздутию кода, которое сделает приложение ещё медленнее.
24. Виртуальные функции.
Мы сделали класс «Y» закрытым для наследования, и написали два метода, которые вызывают метод «B» класса «Y». В методе Slow мы объявляем тип переменной как «X», и при вызове метода «B» у экземпляра класса «Y» компилятор генерирует код обращения к виртуальной таблице, потому что класс «X» открыт для наследования.
Напротив, в методе Fast компилятор знает, что класс «Y» закрыт для наследования, а также он знает, что этот класс переопределят метод «B». И это говорит ему, что неоднозначных ситуаций здесь быть не может, есть только единственный метод «B» класса «Y», который будет вызван в таком случае. Поэтому, надобность в обращении к виртуальной таблице отпадает, не смотря на то, что метод «B» виртуальный. В таких случаях компилятор сгенерирует код прямого вызова метода «B», что положительно скажется на производительности.
Трюки и уловки
25. Reverse engineering.
Иногда производительность приложения упирается в третесторонние компоненты, на которые вы не можете никак повлиять. Или всё-таки можете. Приведу реальный пример, SqlDataReader в методе GetValue для колонки с типом данных «xml» в конечном итоге вернёт вам String либо XmlReader. А задача состоит в том, чтобы сравнить два значения типа «xml» из двух разных источников. Вроде бы ничего особенного – взял да и сравнил две строки, да ещё можно использовать StringComparison.Oridnal чтоб вообще быстро было. Однако если копнуть глубже, и посмотреть в реализацию SqlDataReader, то можно узнать что на самом деле XML данные приходят клиенту в бинарном формате. Это значит, что конвертация таких данных в строку требует дополнительных ресурсных затрат, немалых затрат. Но если потрать время и покопаться в реализации, то можно заставить SqlDataReader думать (с помощью рефлексии), что вместо «xml» ему приходит обычный тип «varbinary». Таким образом, вам теперь просто необходимо проверить два массива байт на равенство, и никаких конвертаций. Конечно, если два массива отличаются, и вы хотите сравнить XML без учёта пробелов, комментариев, и регистра символов, то достаточно (с помощью той же рефлексии) создать два XmlReader на основе полученных байт, и сравнивать элементы XML.
26. Лишние действия.
Если вы играли в какую-нибудь 3D компьютерную игру, и случайно (или специально) попали за предел уровня, то вы, скорее всего, видели что пустое пространство ни чёрное и ни белое, а содержит мусор от предыдущих кадров. Этот эффект является результатом оптимизации игрового движка.
В 3D графике результат отрисовки 3D мира помещается в некий буфер кадра. Если вы загляните в любой туториал для начинающих, то там будет примерно такой код: залить буфер кадра белым или чёрным цветом, нарисовать 3D модель. В таких примерах заливка цветом служит просто фоном, на котором посередине вращается какая-нибудь фигура.
А теперь вспомните любую игру. Вы помните хоть где-то монотонный фон? Особенно, если действия разворачиваются в секретном бункере нацистов. Из этого следует, что перед отрисовкой сцены нет никакого смысла заливать буфер кадра каким-либо цветом, пусть там остаётся предыдущий кадр – всё равно весь кадр будет перетёрт новым изображением. А ведь Full HD (1920×1080) кадр с 32-битной глубиной цвета – это почти 8 MiB! И чтобы залить все однородным цветом, тоже нужно время. Хоть и небольшое, но нужно, причём делать это надо на каждом кадре (а их может быть около 100 в секунду). Таким образом, отсутствие элементарного бесполезного действия очень позитивно сказывается на FPS в игре.
Как это относится к .NET Framework? На самом деле, это относится к любой программе, написанной на любом языке. Вся операционная система просто кишит кучей бесполезных действий, которые выполняются тысячи раз в секунду. Но это отдельная тема, поэтому остановимся на тривиальном примере – оператор new. При создании нового экземпляра класса все его поля установлены в 0 или null, а в массиве все элементы тоже установлены в 0. Мы принимаем это как должное, и должен согласиться да – это очень удобно, и если бы значения были случайными, то это привело бы к множествам ошибок. На самом деле, на уровне системы память выделяется в таком виде, какой была освобождена – при освобождении никто не заботится об обнулении байт. Т.е. платформа .NET в пользу удобности делает действия, которые обычно лишние при выделении больших блоков памяти. Зачем инициализировать массив байт в ноль, если такой буфер будет заполнен данными с диска?
Решением могло бы стать использование GlobalAlloc (без флага GMEM_ZEROINIT) и GC.AddMemoryPressure, но при оптимальном подходе операция выделения памяти не будет слишком частой, поэтому инициализацией в 0 можно пренебречь. Речь в этом пункте, конечно же, не о памяти, а о выполнении бесполезных действий, так что суть вы уловили.
Изобретение велосипеда
27. Реализация «Format» и «ToString».
Не заставляйте программу искать куда и в каком виде вы хотите вставить строковое значение при использовании String.Format, если вы заранее знаете. Перепишите код на String.Concat или StringBuilder – будет быстрее. Лучше не представлять сколько действий выполняется, чтобы найти в строке “<0>” и подставить значение в указанном формате.
Для особого класса задач, можно и заняться реализацией ToString. Например, в разработанном приложении для формирования DML инструкций (INSERT/UPDATE/DELETE; во второй части статьи я объясню что к чему) были написаны свои методы для конвертации значений всех типов данных SQL Server в строку, которые конечно-же не создавали новый объект String. Такая оптимизация дала прирост производительности в 3 раза!
0>
28. Слияние алгоритмов.
Другим примером велосипеда станет самодельный лексер для языка T-SQL, который необходим был для подсветки синтаксиса в текстовом редакторе. При получении очередного токена типа identifier необходимо определить, является ли он ключевым словом. Организовать список всех ключевых слов проще всего в Hashset. Тогда, чтоб проверить является ли токен ключевым словом, достаточно написать:
- Метод «ToUpperInvariant» создаёт новый экземпляр String.
- Метод «ToUpperInvariant» сам по себе очень тяжеловесный в реализации.
- При совпадении хэш кодов, сравниваются две строки. Хоть и побайтно, но сравниваются.
И никаких новых экземпляров строк, никакой сложной логики ToUpper для code points, которые не могут быть использованы в ключевых словах, да и хэш код сразу готов. На самом деле тут можно всё ещё намного круче оптимизировать, но это уже выходит за рамки данного пункта.
29. Изобретайте и экспериментируйте!
- Генерировать скрипт на диск, при этом делать индексный файл, который указывает на смещение от начала файла до каждой строки в тексте.
- Сделать свой компонент text view, который не грузит всё в память, а запрашивает конкретные строчки для отображения (для этого нужен индексный файл).
- Делать лексический разбор скрипта асинхронно, уже после того как первые строчки будут отображены в text view. О своём прогрессе лексер сообщает text view, который обновляет раскраску текста.
- Лексер обычно имеет состояние, которые выражается простым целочисленным числом «int». Зная это состояние и позицию в тексте можно продолжать синтаксический разбор с указанной позиции. А чтобы не хранить все токены для каждой строчки, которая сейчас не отображается на экране, достаточно хранить состояние лексера на начало строки. Как только строку необходимо будет отобразить на экране, необходимо сделать её лексический разбор, используя известное состояние. Это происходит очень быстро, и экономит огромное кол-во памяти. Поэтому, задача асинхронного лексера – разобрать весь текст и сохранить состояние для каждой строки в том же индексном файле, который используется для хранения смещений строк от начала текста. Таким образом, на каждую строку в индексном файле приходится 8 байт: 4 – смещение, 4 – состояние.
30. Вопреки всему.
Хочу отметить немаловажный факт, что приложение после серьёзной оптимизации может измениться в архитектуре некоторых компонентов, причём это зачастую противоречит многим известным вам шаблонам проектирования. Вот утрированный пример – класс, который перемножает числа:
Во-первых, непонятно почему метод «Multiply» не принимает параметров, но возвращает некий результат умножения. Во-вторых, нарушена инкапсуляция. Именного такого рода, казалось бы, казусы могут иногда встречаться после тотальной оптимизации, причём они имеют серьёзную аргументацию. Хочу повторить, что этот пример просто показывает, как могут нарушаться понятия и принципы ООП, и не представляет особой ценности.
Если производительность крайне важна, не бойтесь изменять код до такой степени, что его можно назвать «вонючим». Однако имейте в виду, такие подходы очень плохо отражаются на простоте и понимании архитектуры, кода и его связанности, поддержки и развитию приложения, и зачастую ведут к большому кол-ву ошибок.
От слов к делу
Чтобы не быть голословным, я хочу показать, как применил все вышеописанные принципы (именно все, даже больше) при разработке движка сравнения и синхронизации, которая заняла у меня примерно 5 месяцев. К сожалению, я не могу рассказать обо всех секретах, раскрыть имя компании и имя продукта – статья не для рекламы. Чтобы не приводить просто цифры, вначале я хочу разъяснить для чего нужно такое приложение, и описать общий принцип его работы.
Сравнение и синхронизация баз
- Модель, которая описывает поведение и содержимое базы (таблицы, представления, процедуры, и т.д.; назовём эту часть database schema)
- Собственно данные базы, которые хранятся согласно модели (в основном, табличные данные; назовём эту часть database data)
Общий алгоритм
- Выбрать два источника данных для сравнения. В основном, источниками являются «живые» базы, однако это может быть и файл резервной копии, либо другой альтернативный источник.
- Построить программную модель схемы из выбранных источников. Для сравнения, необходимо знать какие таблицы есть в базе, какие у них колонки, и какие типы данных там хранятся.
- Настроить соответствие таблиц между двумя источниками. В каждой базе есть свой набор таблиц, и необходимо сравнивать таблицу Customers из первой базы с таблицей Customers из второй базы, таблицу Products – с таблицей Products, и так далее. Такое же соответствие необходимо установить между колонками в выбранных парах таблиц.
- Сравнить данные таблиц. В основном, сравнение происходит по ключевым колонкам – они отвечают за уникальность записи в таблице. В результате такого сравнения мы можем получить 4 основных набора записей:
• существующие только в первой таблице (нет схожих записей во второй)
• существующие только во второй таблице
• различные записи (данные отличаются в не-ключевых колонках)
• идентичные записи (данные во всех колонках идентичны)
Все эти записи (кроме идентичных), необходимо сохранить на локальный диск в некий кэш, который будет использован в следующих шагах. - Анализ сравнения, настройка синхронизации. На этом этапе пользователь может заняться анализом разницы, и выбрать таблицы и записи для частичной синхронизации, а также настроить различные опции для достижения желаемого результата. На этом этапе так же формируется план действий для синхронизации (удалить 10 записей там, обновить 5 записей тут, вставить 50 записей туда, и т.п.)
- Выполнение синхронизации. По построенному плану и кэшу записей из предыдущих шагов, формируется SQL скрипт, который применяет изменения. Его можно выполнять на лету по мере формирования, либо просто сохранить на диск для отложенного выполнения.
Быстродействие в цифрах
- Intel i7-2630QM (4 cores @ 2GHz, 2.9GHz in Turbo, 8 logical threads)
- 12 Gb RAM DDR3 @ 1333 MHz (9-9-9-24)
- Intel SSD X25-M 120 Gb (250 Mb/s read, 35000 IOPS; 100Mb/s write, 8600 IOPS)
- Microsoft SQL Server 2008 R2 Developer Edition 64-bit
- Windows 7 Ultimate x64
В итоге
Каждая задача требует индивидуального подхода, а оптимизация её решения включает комбинации различных концепций. Существует намного больше других принципов, которые не освещены в данной статье, некоторые из которых попросту не могут быть применимы к платформе .NET.
Есть много споров о сравнении быстродействия платформы .NET с приложениями, написанных на C++. За мою жизненную практику, в сложных алгоритмических задачах связка C++ и Assembler дают выигрыш от 1.5 до 2.5 раз по сравнению с C#. Это значит, что критические части можно выносить в unmanaged DLL. Однако на реальном примере я показал, что и на C# можно писать очень эффективный код.
И всё же не стоит досконально оптимизировать каждый кусок кода там, где это не требуется. Многие принципы ведут к усложнению архитектуры и понимания кода, а также поддержки и расширения вашего приложения, что выливается в кол-во потраченного времени. В данной статье я не призываю вернуться в каменный век, отказавшись от всех удобств платформы .NET, а просто хочу показать, как можно делать простые вещи сложно, но очень эффективно. Всегда вначале стоит определиться с целью приложения, требованиями к быстродействию и бюджетом, а потом решать как это реализовывать.
Очень хотелось по каждому из пунктов написать поподробнее, добавить кучу изображений и примеров для лучшего понимая, но тогда это получилась бы не статья, а книга. Тем не менее, я надеюсь, что она поможет в первую очередь тем, кто вообще не знает в какую сторону смотреть, если стоит задача оптимизации кода высокопроизводительного приложения.
Несколько советов по оптимизации кода на C++
1) Вместо использования цикла for() быстрее использовать цикл while(). По той простой причине, что while() был придуман раньше, чем for(), то следовательно while() более низкоуровневый, а следовательно за абстрагирование мы расплачиваемся меньше, что ускоряет наш код.

2) если честно, то данной код можно ускорить ещё больше. поскольку оператор ++ является неоптимальным, лучше использовать его аналог =.
Так делали ещё наши деды на Паскале и Фортране, получая код, который просто летал. Я вам больше скажу, при программировании процессоров, работавших на советских космических станциях использовали именно этот оператор. И ведь где, а там нужно высокая эффективность кода.

3) Если вам нужно сделать вечный цикл, например как при поиски в ширину, лучше писать while(!false) вместо обычного while(true). поскольку процессор быстрее воспринимает команды с нулями, то и отрицание нуля он будет обрабатывать быстрее, нежели просто единицу. Это сильно ускоряет ваш проход по циклу.

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

5) Ну и последний совет по оптимизации заключается в том, чтобы сократить потребление оперативной памяти при исполнении программы. ОБЯЗАТЕЛЬНО удаляйте все лишние пробелы, табуляции, переходы на новые строки и комментарии. Именно они зачастую являются причиной потребления большей части оперативки и кеша процессора. Вы сами можете увидеть, как код стал более компактным и более читабельным. Если ваш код будет читать другой программист, то на его экранном пространстве также он будет занимать меньше места, соответственно полезного кода будет помещаться больше.



53 поста 4.6K подписчика
Правила сообщества
Соблюдайте правила Pikabu:
Помимо этого ЗАПРЕЩЕНО:
— Размещать в сообществе посты стиля «Подскажите как удалить вирус», «Подскажите как установить программу», «Подскажите как починить монитор/телевизор/мышь/тостер/стиральную машину» или «Напишите за меня лабу в универ». Пожалуйста размещайте такие посты вне этого сообщества или в соответствующих для этого сообществах.
Существенный недостаток нового дизайна пикабу: пока я дочитал до тегов, у меня уже стул сгорел.
Чувак, нужно было тоньше, вот прям чтоб на грани, тогда бы половина не поняла бы, начался бы лютейший срач и полыхали бы пуканы
А я почти поверил, что этот бред серьезно написан 🙂
Срочно переводи это на индийский
Прочитал, влепил минус, начал писать гневный комментарий, затем у видел тег. Не пугай так больше.

Ответ на пост «Как полезно и важно порой чувствовать себя идиотом»
На мой взгляд, проблема обучения программированию, что дают знания не обучая отладке, особенно в универе. Редко кто говорит о типичных ошибках начинающих программистов. Редко задачу дробят, чтобы объяснить (Особенно при вложенных циклах for). Никогда не забуду про операции с матрицами в C. Редко делают разбор нескольких сложных задач. На обычных курсах дают пару тривиальных примеров , а в домашнем задании примеры не соответствуют уровню пройденного занятия и сложность задач растет в геометрической прогрессии. Частая проблема новичков в невнимательности и преподавателей это сильно раздражает. Программирование на С/С++ в начале 00-ых мне далось очень тяжело, сказалось отсутствие ПК дома. Преподаватель по С (по совместительству бывший завкафедры и к.т.н) при группе сказал , что такие как я не доучиваются (ладно бы если сам про себя сказал,а тут публично считай назвали идиотом), но так получилось я всё-таки доучился , а те кто знал лучше меня C далеко не все доучились. Я тогда взял себе за правило, если буду когда-либо помогать в обучении, то всегда буду относиться обучающемуся с уважением (без снобизма, подколок), если возникает непонимание, переформулировать иначе и кроме того я обучал на реальных задачах. Если студент не понял и/или ошибается, это не только проблема студента, но и того кто обучает. Сейчас немного программирую на Python, занимаюсь парсингом при помощи xpath и regex. Иногда помогаю в изучении питона знакомому школьнику. По его словам обучение со мной оказалось гораздо интереснее и полезнее чем с преподавателем. К счастью или к сожалению, программирование начинается сколько не с программирования , а прежде всего с умения анализировать (в точнее умении декомпозировать задачу), умении сконцентрироваться и не отвлекаться (по началу списать код с учебника без ошибок сложно), многократном повторении пройденного материала, в самостоятельном поиске информации, умении задавать правильные вопросы и запросы в поисковой системе.

Ребята в недосягаемости

Первая встреча с ужасных кодом


Ответ на пост «Как программисты пишут код?»
Есть два типа людей: одни могут писать код, другие нет. Те, которые могут, делятся на еще на два: те, кто сразу видят решение, и те, кто итеративно работает.
Я в разработке с 2007 и всякого дерьма повидал. За сим есть, кой-чего сказать. Те, кто сразу видит решение, их меньшинство. По моему опыту, не более 10-15%. Остальные — только через итерации, либо фрагментарный подход.
Комплексный подход. Программист сразу пишет примерно 80% кода, можно сказать, на одном дыхании. Далее — косметика, марафет, отладка. В 99% первоначальный код не меняется. Это, имхо, — либо врожденная способность, либо нечто приобретенное в сильно раннем детстве.
Итеративный подход. Программист пытается хоть как-то решить задачу. Криво, косо, но решить. Далее начинаются итерации, которых может быть очень много в засимости от сложности и погружения разработчика в предметную область. Это обычный подход среднестатистического работника, коих на рынке большинство. Имхо, таких 75-80%.
Фрагментарный подход. Программист пытается декомпозировать код, ибо сразу он его обработать не в силах. Далее идет пошаговая реализация различных его кусков, после чего попытка связать все воедино, что, как правило в 99.99%, приводит к значительным переработкам ранее готовых кусков кода. Это следствие неприспособленности мозга. Т.е. человек может писать код, но ему это очень сложно дается.
От себя еще добавлю, что современная реальность требует всех видов людей: каждому программисту найдется место, если он все-таки по итогу выдает рабочий код.

Ответ на пост «Как программисты пишут код?»
Вопрос очень интересный.
Думаю, программирование тут вообще не причем. Это просто один частный пример применения определенных способностей. А в общем это умение создавать что то «своими руками». Лично я мыслю абсолютно одинаково и когда занимаюсь программированием и когда планирую ремонт дома. Честно говоря, это происходит полностью интуитивно. Я практически всегда знаю какие общие и конкретные шаги надо предпринимать чтобы достигнуть нужных целей и сразу же представляю где могут быть проблемы и уже отдельно обдумываю варианты их решения. Понятное дело тут не последнюю роль играет опыт, но когда 25 лет назад начинал на бейсике и паскале, то мыслил точно также.
С другой стороны, тут на пикабу, узнал что люди покупают наборы лего, собирают их точно по инструкции и ставят на полку(понятное дело что все люди разные и я не в негативном ключе пишу). Для меня в детстве это было бы немыслимо. Лего у меня не было, но был похожий отличный советский конструктор. Даже если бы у меня была к нему инструкция, то я бы все равно строил что то свое. Кстати мыслительный процесс при этом очень похож на программирование.
Ну и самое главное — меня прет от процесса. И думаю всех, кто считает себя серьезным профессионалом в своей сфере. И началось это с самого первого раза, когда я на спектрумовском бейсике часики рисовал. Это дает возможность не останавливаться, если что то не получается. Любой баг, причин которого ты не понимаешь, становится персональным врагом. И когда ты наконец решаешь проблему, то организм радует тебя дофамином.
Я очень сомневаюсь, что этим конкретным навыкам и способностям можно научить, как ни крути. Можно толь дать базу тем, у кого они уже есть — думаю на всяких инфоцыганских курсах «войти в ИТ» каких либо положительных результатах добиваются только граждане которые сразу программировать умеют, только еще не знают об этом)
А еще хороший программист всегда ленив. И это тоже напрямую влияет на умение «писать код». Основная и, не побоюсь этого слова, основополагающая проблема в любом серьезном проекте не как что то сделать, а каким образом это сделать в контексте уже написанного и так, чтобы в будущем тоже можно было что то изменить. Лень позволяет сразу планировать все так, чтобы минимизировать труд потом.
Резюмируя, думаю что есть некий набор врожденных качеств и способностей и позволяет некоторым успешно работать программистами. Собственно это верно и в любой другой сфере )))

Python — высокоуровневый язык, потому что у него высокий уровень заботы



Разработчикам на «плюсах» посвящается:

Лучшее объяснение про оптимизацию современного софта


Автоматизация Excel с помощью VBA на примере графика отпусков
(Офисной оптимизации пост (теперь уже с примерами))
В прошлом своём посте (где рассматривал, что есть VBA в Excel и зачем это может пригодиться) целых 137 человек подписалось на меня, в комментариях были призывы к каким-нибудь примерам использования VBA, да и обещал я @Tiafreed подкинуть материалов для ВКР, так что набросал за ночь простенький (в сотню строк кода без использования массивов, классов и т.д.) файлик в Excel с VBA модулем. Пост разделю условно на две части: для пользователей, кому интересно просто посмотреть как выглядит, что делает, плюс скачать, поиграться и для продвинутых пользователей, кому интересно как это работает и как настроить подобное под себя. Цель поста — показать возможности VBA (частично), предложить интересный вариант реализации достаточно распространённой задачи по расчёту отсутствия сотрудников.
Если формат поста зайдёт, то в следующий раз набросаю пример, как формировать Word документы из списка данных в Excel, используя шаблон и пользовательскую форму (и не используя ублюдскую рассылку ИМХО).

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

*Все персонажи вымышлены, совпадения случайны
Дальше идём на другой лист, нажимаем кнопку

После чего идут расчёты какое-то время (у меня это где-то половина секунды)

Машина рапортует нам об успешном завершении своей миссии, идём смотреть, что вышло.

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

Тут всё ещё проще, кнопка для запуска, табличка с примитивными расчётами (формула МАКС) и график на 366 дней который можно с лёгкостью оформить самому и с помощью которого отлично видны провалы и пики нагрузок. Нажатием на выпадающий список, мы выбираем отдел по которому выводятся данные. Вот и всё, просто и удобно. Набросал за пару ночных часов. Сразу предупреждаю, что я это не предлагаю, как готовый продукт (успешное бизнес-решение ваших кадровых проблем), просто накидал маленький пример и делюсь им с вами, потому ответственность за его использование и обслуживание не несу, но если есть желание доработать его в своих целях, готов подсказать и помочь. Да, если возникла ошибка, вероятнее всего, что формат даты/числа нарушен, защиту от дурака не ставил, ибо цели чисто демонстрационные, но если вдруг мой косяк (протестить нет возможности) перезалью и ссылку в комментарии кину. Вот сам файл (на свой страх и риск :D, никаких гарантий, что будет работать). https://yadi.sk/d/lsRdKL8wQ42FFw (и не забываем включить макросы)
Сразу отвечу на вопрос — нахрена на VBA можно же на формулах? Да, можно, но так динамично (легко добавлять/убирать людей/отделы), наглядно и расчёт каждого дня формулами будет очень сильно грузить проц, а так мы считаем лишь раз, когда кнопку нажимаем.
Тэкс. Теперь вторая часть, в принципе, дальше можно не читать, так, для очистки совести её пишу. Кому интересно, как это работает или как вообще выглядит VBA на практике. Всё просто, в основе лежит вот эта строка (в ней мы будем искать колонку с датой отпуска и уже в ней работать)

Сначала мы подготавливаемся, что-то где-то очищаем, что-то добавляем (всё в общем-то закомментил) и сортируем строки по отделам

Потом запускаем цикл перебора строк с сотрудниками, в этом цикле для каждого работника мы проверяем, является ли он началом нового отдела, если да, то делаем разделитель, если нет — кладём болт и идём дальше, дальше рассчитываем отпуска, каким образом? Берём дату начала и ищем её в строке с датами, находим (или не находим и крашимся, если закосячили, не стал пилить защиту от дурака), берём эту ячейку как точку начала, прибавляем количество дней отпуска, отнимаем один (ибо включительно) и это наша точка окончания, объединяем эти ячейки, окрашиваем, в этих столбцах делаем простые расчёты (+1 к каждому дню и перерасчёт процентовки). После прохода по всем персонажам просто копируем полученные цифры на главную страницу, чтобы подставлять их в график. Всё, почти.


И простейший обработчик для выпадающего списка — просто вставляем в строку из которой берёт данные график данные из нужной нам строки. Рассчитываем её как номер строки начала (у нас 22) + номер элемента выпадающего списка (нумерация идёт с нуля у listindex)

P.S. Знаю что говнокод, так что буду рад конструктивной критике, чему-то подучиться у более знающих. В свою защиту скажу, что код писался на халяву, без обработчика ошибок, заплаток, защиты от выстрелов в ноги, ограничений действий, фиксирования диапазона вводимых данных, добавления полосы загрузки и всё такое, чисто для демонстрации. Спасибо, если дочитал это до конца.

Не только финансовая система может держаться на Excel
(Офисной оптимизации пост, точнее, об одном из инструментов этой самой оптимизации)
Привет Пикабу, увидел недавно забавный, но правдивый пост про Excel и решил немного поделиться информацией об этой интересной программе. Предупреждаю, что пост будет длинным и не на ширпотреб, но постараюсь не кидаться терминологией и не вдаваться в тонкости.

Так вот, многие даже и не подозревают, сколько всего может на нём держаться, — не только российская мировая финансовая система, но и, например, целый каскад программного обеспечения в какой-нибудь организации. Сам я работал айтишником в одной организации и ввиду специфики работы всей компании, довелось разрабатывать ПО (программное обеспечение) на Excel (да, на этих страничках с табличками, где, как я думал ещё в школьные годы, проводятся всякие узкоспециализированные записи, ну кто будет оформлять документ в какой-то неудобной таблице), профи в области Excel себя не считаю, ибо есть куча белых пятен вроде финансовых функций и надстроек в которых всегда найдутся люди, которые разбираются лучше меня. Но это так, отступление.
Сами по себе формулы — очень мощный инструмент, на умении их использовать вывезло столько оптимизаторов, не владеющих программированием, что, думаю, нет еще офиса, где какой-нибудь местный Кулибин в обеденный перерыв не замутил еще какую-нибудь узкоспециализированную считалку для отдела. Но, работая с большими массивами возникает ряд проблем в использовании формул: относительность (ты получаешь не строку информации в базе данных, а динамичный результат вычисления) и оптимизация. Да, оптимизация на нескольких тысячах строк с десятком колонок и, допустим, парой связанных таблиц, это беда. Такая связка на i3 4гб оперативы просто будет повергать бедный офисный комп в ужас, заставляя его терять сознание при каждом пересчете и вылетать, если ты нарушил священный ритуал пятиминутного сохранения (знал я одного мужика, у него была такая формульная портянка, что сохранял базу он лишь два раза в день, перед обедом и уходя домой, ибо на сохранение уходило минут 20, ненавидели мы его все, ибо, уходя в отпуск, он оставлял это чудище кому-то из нас). Специфика работы была такова, что интернета у нас не было, а стороннее ПО нельзя — пользуйтесь чем дали. Окей, но автоматика же нужна, без неё никуда, потому пришлось использовать то, что есть и открывается это:

Разработчикам показалось мало создать мощный инструмент формул, они решили впаять в и так могущественное ПО целый язык программирования (точнее его диалект) VBA (Visual Basic for Application), возможность подключать модули с других языков, использовать API (хоть и работает это ИМХО через жопу) и встроенную среду разработки (а это означает, что вообще ничего не надо качать, если у Вас есть офис, значит все что нужно, чтобы стать мамкиным программистом уже есть). Но что нам это даёт? Огромные (ну это как посмотреть) возможности для разработки ПО, преимущественно узкоспециализированного, без использования чего-либо кроме Excel; базы данных? О чём речь, Excel — это и есть БД (то ещё извращение, но для утонченных можно связать с Access или Sql), возможность проводить расчёты (циклы, тонкие переборы, фильтрация) над большими массивами информации в кустарных условиях, использовать встроенные библиотеки для работы с другими приложениями (самое важное — MS Word), возможность наконец применить Visual Basic, который ты учил лет 20 назад, а он нигде так и не пригодился, ну и самое главное, научиться основам программирования, если ты что-то шаришь, но твои лучшие успехи — верстка шаблонов сайтов на HTML, CSS с вкраплениями PHP. Также это нам даёт возможность кодить на ведре (прям совсем ведре-ведре). VB хоть и относится к ООП, но де-факто работа в нём редко сводится к пользовательским классам, всяким тонкостям и т.д., в основном он ощущается как скриптовый язык, работа приходит к чему:

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

Немного возни и у нас есть пользовательская форма со списком сотрудников и полями, которые надо заполнить, на выходе по нажатию кнопки мы получаем готовую вордовскую справку. Для этого нужен лист с данными сотрудников, лист список справок и шаблон MS Word. Готово, вы бог офисной оптимизации. И так можно многое: отчёты, справки, товарные чеки, письма, документы разные, приказы целые, любые действия с информацией.
Сразу предупрежу всех, кто уже нацелился писать «на кой хрен ты раскопал этот старый кусок говна на заре 30го десятилетия 21го века». Пост чисто информативный, это не гайд, не самоучитель, может кому интересно, на прорыв в IT сфере ни разу не претендует, это раз, есть в нашей стране места, где развитие этой сферы отстаёт как раз на эти 20 лет, это два, ну и просто, может кому понадобится, может кто-то захочет на работе чему-то подучиться.
Какие минусы? Оптимизация всё равно сосёт бибу (но не такую, как формулы), безопасность тоже, чисто теоретически можно использовать криптографическое шифрование БД и расшифрование в ходе работы, но я не проверял, можно ли легко вскрыть защиту самого VBA проекта, да и оптимизация пососёт ещё большую бибу (да и вообще, кому это надо, ребят, это же Excel), ну а стандартную Excel защиту листов можно вскрыть обычным архивом и блокнотом. Также, недостатком я считаю ряд ограничений среды, по типу того, что без API не работает прокрутка колёсиком мыши, стабильность — excel иногда любит чудить. Совместимость — отдельные танцы с бубном для x64 и x32 (но это если используете сторонние API, модули). Ну и объяснять людям, как разрешить запуск макросов :D.
Спасибо, если дочитал этот длинный (и наверное скучный) пост до конца, если вдруг кому стало интересно могу написать ещё много чего, например, как написать сапёр на Excel, как сделать различные простенькие, но очень нужные офисные программки, как научиться этому (но.. зачем?), как использовать макрорекордер и много чего ещё, связанного с Excel. А ведь это всё ещё цветочки, есть люди, которые целые стратегические пошаговые игры в ячейках писали на том же VBA.
Как ускорить набор кода и сделать его без ошибок?
Как использовать сниппеты в Visual Studio? Как создавать свои? Как посмотреть, какие уже созданы? Попробуем ответить на все эти вопросы.
Сниппеты— это шаблоны кода, которые позволяют нам вместо полностью ручного ввода команд, использовать короткие сочетания, для того, чтобы сразу получить достаточный объем кода. Создаются они обычно для того, чтобы ускорить время разработки и тратить время на решение интересных задач вместо того, чтобы набирать тот же старый скучный код. например:
При нажатии два раза на «Tab» за нас машина заполнила полностью всю команду.
- вставлять «кусок» команды;
- окружить код, вокруг которого он будет использован.
Обычно используют клавиатурные сочетания, но можно использовать и без него:
Мы «завернули» команду Console.WriteLine в If.
Так же сниппеты позволяют использовать параметры, как в данном случае с False:
Еще, при помощи сниппетов мы можем вставлять комментарии в начале метода.
Теперь метод помечен надписью, которую мы внесли в комментарии, как и в аргумент.
Где они находятся?
В Tools->Code Snippets Manage… ->
Обратите внимание, что сниппеты есть не только С#, но и в других языках программирования.
В папке Visual C# находятся сниппеты, которые есть по умолчанию.
Можно выбрать сниппет, посмотреть его описание, клавиатурное сочетание. Чтобы посмотреть код шаблона, нужно найти папку где они находятся. Копируем адрес из окна «Location» и вставляем в проводник, перетаскиваем сниппет (например, «If») в окно программы. Перед нами код шаблона.
Можно ли создавать свои сниппеты?
Да, можно, это позволяет достаточно сильно экономить время. Но это не просто команда CNTRL+C и CNTRL+V, сниппеты позволяют использовать параметры, и кое-что еще.