Как ускорить работу приложения c
Как говорит дитя, мой вопрос заключается в том, как я могу сделать это быстрее (будет много входных данных). Любая помощь, которая может включать использование другого алгоритма или использование различных функций или изменение всего кода, принимается.
Я не очень разбираюсь в C ++ Standard и, следовательно, не знаю обо всех различных функциях, доступных в разных библиотеках, поэтому, пожалуйста, объясните свои причины, и если вы слишком заняты, по крайней мере, попытайтесь предоставить ссылку.
Это работает очень просто. Цикл while будет выполняться до тех пор, пока пользователь не введет символ. Как я объяснил ранее, программа принимает 3 числа и выводит наибольшее. Нет другой части этого кода, это все. Я пытался объяснить это как можно больше. Если вам нужно что-то еще от меня, пожалуйста, спросите, (я постараюсь, сколько смогу).
Я собираю на интернет-платформе с использованием CPP 4.9.2 (это то, что там сказано)
Любая помощь будет высоко оценена. заранее спасибо
Ввод осуществляется с помощью компьютера, поэтому нет задержки ввода.
Также я приму ответы на с и с ++.
Я также хотел бы спросить, существуют ли какие-либо общие библиотечные функции или алгоритмы, или какой-либо другой совет (определенные вещи, которые мы должны делать, а что нет), которым нужно следовать для ускорения выполнения (не только для этого кода, но и в генеральный ). Любая помощь будет оценена. (и извините за то, что задал такой неловкий вопрос, не дав никаких справочных материалов)
Решение
Ваш «алгоритм» очень прост, и я бы написал его с использованием max() функция, только потому, что это лучший стиль.
Что займет больше всего времени, так это scanf , Это ваше узкое место. Вы должны написать свою собственную функцию чтения, которая читает огромный блок с fread и обрабатывает это. Вы можете сделать это асинхронно, но я бы не рекомендовал это в качестве первого шага (некоторые асинхронные реализации действительно медленнее, чем синхронные реализации).
Итак, в основном вы делаете следующее:
Другие решения
Если у вас нет гарантии, что все три входных числа различны, я бы беспокоился о том, чтобы программа получала правильный вывод. Как уже отмечалось, ускорять практически нечего, кроме буферизации ввода и вывода и, возможно, ускорения десятичных преобразований с помощью пользовательского кода синтаксического анализа и форматирования вместо общего назначения. scanf а также printf ,
Вы можете минимизировать количество сравнений, запоминая предыдущие результаты. Вы можете сделать это с:
Это делает ровно 2 сравнения, чтобы найти значение для d как max(a,b,c) ,
В этом документе описываются некоторые рекомендации по оптимизации программ C++ в Visual Studio.
Параметры компилятора и компоновщика
Профильная оптимизация
В Visual Studio поддерживается профильная оптимизация (PGO). Она использует данные профиля из обучающих выполнений инструментированной версии приложения для последующей оптимизации приложения. Использование профильной оптимизации может занимать много времени, поэтому к ней может обращаться не каждый разработчик, однако рекомендуется прибегать к такой оптимизации для окончательной сборки выпуска продукта. Дополнительные сведения см. в статье Профильные оптимизации.
Кроме того, были усовершенствованы оптимизации всей программы (создание кода во время компоновки) и оптимизации /O1 и /O2 . В целом приложение, скомпилированное с одним из этих параметров, будет работать быстрее, чем такое же приложение, скомпилированное с помощью более ранней версии компилятора.
Какой уровень оптимизации следует использовать
Если это возможно, окончательные сборки выпуска должны быть скомпилированы с помощью профильной оптимизации. Если выполнить сборку с помощью профильной оптимизации невозможно из-за недостаточной инфраструктуры для запуска инструментированных сборок или отсутствия доступа к сценариям, рекомендуется выполнить сборку с помощью оптимизации всей программы.
Параметр /Gy также очень полезен. Он создает отдельный COMDAT для каждой функции, предоставляя компоновщику большую гибкость при удалении COMDAT без ссылок и свертывания записей COMDAT. Единственный недостаток использования /Gy состоит в том, что он может приводить к возникновению проблем при отладке. Поэтому, как правило, его рекомендуется использовать. Дополнительные сведения см. в статье /Gy (включение компоновки на уровне функций).
Для связывания в 64-разрядных средах рекомендуется использовать параметр компоновщика /OPT:REF,ICF , а в 32-разрядных средах рекомендуется использовать /OPT:REF . Дополнительные сведения см. в статье Параметр /OPT (оптимизация).
Кроме того, настоятельно рекомендуется создавать отладочные символы даже в оптимизированных сборках выпусков. Это не влияет на созданный код и значительно упрощает отладку приложения.
Параметры с плавающей запятой
Параметр компилятора /Op был удален. Добавлены следующие четыре параметра компилятора для оптимизаций с плавающей запятой:
Параметр | Описание |
---|---|
/fp:precise | Это рекомендация по умолчанию, которую следует использовать в большинстве случаев. |
/fp:fast | Рекомендуется, если производительность имеет первостепенное значение, например в играх. При использовании этого параметра достигается максимальная производительность. |
/fp:strict | Рекомендуется, если требуются точные исключения для плавающей запятой и требуется поведение IEEE. При использовании этого параметра достигается минимальная производительность. |
/fp:except[-] | Его можно использовать вместе с /fp:strict или /fp:precise , но не с /fp:fast . |
declspecs оптимизации
В этом разделе мы рассмотрим два declspec, которые можно использовать в программах для повышения производительности: __declspec(restrict) и __declspec(noalias) .
declspec restrict можно применять только к объявлениям функций, которые возвращают указатель, например __declspec(restrict) void *malloc(size_t size); .
declspec restrict используется в функциях, возвращающих указатели без псевдонимов. Это ключевое слово применяется для реализации библиотеки времени выполнения C malloc , так как оно никогда не возвращает значение указателя, которое уже используется в текущей программе (если только вы не делаете что-то недопустимое, например используете память после того, как она была освобождена).
declspec restrict предоставляет компилятору больше сведений для выполнения оптимизации компилятора. Одной из самых трудных задач для компилятора является определение того, какие указатели являются псевдонимами других указателей, а эта информация значительно облегчает работу компилятора.
Стоит отметить, что это гарантированно предоставляемая информация, а не данные, которые компилятору нужно проверять. Если программа использует declspec restrict ненадлежащим образом, она может работать некорректно.
Дополнительные сведения см. в статье restrict .
declspec noalias также применяется только к функциям и указывает, что функция является получистой. Получистая функция ссылается только на локальные переменные, аргументы и косвенные обращения первого уровня к аргументам, а также изменяет их. Этот declspec является обещанием для компилятора, и, если функция ссылается на глобальные переменные или косвенные обращения второго уровня к аргументам указателя, компилятор может создать код, который нарушает работу приложения.
Дополнительные сведения см. в статье noalias .
Директивы pragma оптимизации
Эта директива pragma позволяет задать определенный уровень оптимизации для каждой функции. Она идеально подходит для тех редких случаев, когда приложение аварийно завершает работу при компиляции заданной функции с помощью оптимизации. Ее можно использовать для отключения оптимизации для одной функции:
Дополнительные сведения см. в статье optimize .
Встраивание является одной из самых важных оптимизаций, выполняемых компилятором, поэтому мы поговорим о паре директив pragma, которые помогают изменить это поведение.
__restrict и __assume
В Visual Studio существует несколько ключевых слов, которые могут помочь повысить производительность: __restrict и __assume.
Во-первых, следует отметить, что __restrict и __declspec(restrict) — это две разные вещи. Несмотря на некоторую связь, эти ключевые слова имеют разную семантику. __restrict является квалификатором типа, как, например, const или volatile , но исключительно для типов указателей.
Указатель, который изменяется с помощью __restrict , называется указателем __restrict. Указатель __restrict — это указатель, доступ к которому можно получить только с помощью указателя __restrict. Иными словами, для доступа к данным, на которые указывает указатель __restrict, нельзя использовать другой указатель.
__restrict может быть мощным средством для оптимизатора Microsoft C++, но его следует использовать с большой осторожностью. При неправильном использовании оптимизатор может выполнить оптимизацию, которая приведет к нарушению работы приложения.
С помощью ключевого слова __assume разработчик может сообщить компилятору о необходимости допущений относительно значения определенной переменной.
Например, __assume(a < 5); указывает оптимизатору, что в этой строке кода переменная a меньше 5. Опять же, это точные данные для компилятора. Если на самом деле переменная a в этой строке в программе равна 6, программа после оптимизации, выполненной компилятором, может работать неправильно. Ключевое слово __assume максимально эффективно, если используется перед операторами switch и (или) условными выражениями.
Однако для __assume действуют некоторые ограничения. Во-первых, поскольку __restrict является всего лишь предложением, компилятор может игнорировать его. Кроме того, ключевое слово __assume сейчас работает только с переменными неравенствами для констант. Оно не распространяет символьные неравенства, такие как assume(a < b).
Поддержка встроенных функций
Встроенные функции — это вызовы функций, когда компилятор обладает внутренними знаниями о вызове, и вместо вызова функции в библиотеке он выдает код для этой функции. Файл заголовка <intrin.h> содержит все доступные встроенные функции для всех поддерживаемых аппаратных платформ.
Встроенные функции позволяют программисту более подробно изучать код без использования сборки. Использование встроенных функций имеет несколько преимуществ:
Код является более переносимым. Ряд встроенных функций доступен в нескольких архитектурах ЦП.
Код более удобен для чтения, так как он по-прежнему создается на C или C++.
Код получает преимущество оптимизаций компилятора. По мере повышения эффективности компилятора совершенствуется процесс создания кода для встроенных функций.
Дополнительные сведения см. в статье Встроенные функции компилятора.
Исключения
Поскольку использование исключений может приводить к снижению производительности, воспользуйтесь приведенными далее рекомендациями. Некоторые ограничения вводятся при использовании блоков try, которые не позволяют компилятору выполнять определенные оптимизации. На платформах x86 блоки try приводят к дополнительному снижению производительности из-за дополнительных сведений о состоянии, которые должны быть созданы во время выполнения кода. На 64-разрядных платформах блоки try не так сильно снижают производительность, но как только возникает исключение, процесс поиска обработчика и очистки стека может оказаться дорогостоящим.
Поэтому рекомендуется избегать добавления блоков try/catch в код, в котором они на самом деле не нужны. Если необходимо использовать исключения, по возможности используйте синхронные исключения. Дополнительные сведения см. в разделе Structured Exception Handling (C/C++).
Наконец, вызывайте исключения только в исключительных случаях. Использование исключений для общего потока управления, скорее всего, приведет к снижению производительности.
Данный текст будет посвящён в большей степени тем, кто только делает свои первые шаги в программировании и о производительности совершенно не задумывается. А между тем, практически все основные шаги по увеличению скорости выполнения вашей программы непосредственно связаны с компьютерной грамотностью и пониманием происходящих процессов. Итак, оставим общие фразы и перейдём непосредственно к сути.
Не навреди
На скорость выполнения вашей программы влияет 2 непосредственных фактора:
- Скорость исполнения кода
- Объём выделяемой памяти
Как вы понимаете, минимизировать одновременно влияние обоих этих факторов невозможно: заигрывания с памятью неизбежно приводят к раздуванию кода, а «малобуквенный» текст к повышенному потреблению ресурсов исполняемой машиной. Поэтому главным правилом оптимизации является исключение излишеств в вашей программе. Это означает никакого лишнего кода и строго ограниченное использование памяти. На словах это легко, а на деле?
Операции по оптимизации
Если задача оптимизации встала перед вами уже после написания кода, то наиболее разумным решением будет предварительная очистка и разбиение на части с последующим изучением времени выполнения отдельных блоков. Очистка предполагает удаление неиспользуемых участков, переменных, избыточных заходов циклов.
Так как мы предполагаем, что код уже чистый, то необходимо в первую очередь рассмотреть выполнение следующих правил:
- Минимизировано количество используемых переменных. Например, ваша программа изобилует различными циклами. По правилам красивого кода, вы можете создать для каждого цикла свой уникальный счётчик, но с точки зрения оптимизации – это трата драгоценных ресурсов. Если в одной части программы переменная “отработала”, то её вполне можно применить в другой.
- Правильно выбраны типы данных. Как известно, каждый тип данных имеет свой используемый диапазон, то есть собственно тот размер памяти, который резервируется под его использование. Например, создавая код в Java и имея переменную, способную принимать только два значения (например, «on» и «off»), лучше использовать boolean с созданием последующего соответствия, но никак не char.
- Минимизировано количество присваиваний. Опять-таки, руководствуясь принципами красоты кода, вы можете разбить длинное арифметическое выражение на несколько более мелких. Это чревато появлением избыточных переменных и лишними операциями присваивания, что позитивно на быстродействии точно не скажется.
- Переменные инициируются при объявлении. Это правило хорошего тона в программировании не только повысит производительность, исключая лишние операции, но и избавит от невынужденных ошибок.
- Однотипные повторяющиеся операции объединены в процедуру или функцию. Еще один приём из основ программирования, который, тем не менее, часто игнорируется новичками.
- Однотипные циклы объединены. Допустим, у вас есть несколько массивов одинаковой размерности, которые надо заполнить в цикле. Вы можете создать несколько циклов и повысить читабельность или запихнуть все операции в общий цикл и повысить быстродействие. Решать вам.
- Использованная память немедленно очищается. Безусловно, не стоит удалять каждую переменную сразу после окончания её использования, но когда речь идет о работе с существенным объемом памяти (например, с большими массивами), контролировать потребление ресурсов просто необходимо.
Когда оптимизация не имеет смысла
Безусловно, подобные приемы не являются универсальными, а в случае с рядом языков и компиляторов (как в случае Dart в JavaScript с помощью dart2js), вы и вовсе получите настолько оптимизированный код, что собственными руками вряд ли создадите что-то лучше.
Однако, в любом случае, опыт применения описанных выше приемов поможет новичку перейти из разряда хороших программистов в разряд эффективных, ведь так вы сэкономите не только ресурсы машины, но и свое личное время.
Уже много лет твой компьютер умеет выполнять код любимого ПО параллельно, на всех своих ядрах и процессорах. Да что там компьютер — скоро холодильники смогут обсчитывать гастрономические предпочтения хозяина в несколько потоков, недаром IoT движется по планете семимильными шагами. И казалось бы, уже весь софт давным-давно должен уметь максимально эффективно нагревать атмосферу вокруг тебя, нагружая транзисторы твоего ПК многопоточностью, но все не так замечательно, как хотелось бы.
В мире многопоточности существуют две основные проблемы: не все алгоритмы успешно распараллеливаются, а то, что все-таки удается запустить на нескольких ядрах, работает далеко не так быстро, как хотелось бы. Причина неэффективной работы мультипроцессорного кода (если не говорить про недостаточно прямые руки программистов) кроется в излишних затратах процессорного времени на синхронизацию данных между ядрами. Да-да. Это те самые мьютексы, семафоры и прочее, что мы так привыкли использовать в нашем нехитром ремесле.
Из этого следует простой вывод — если есть возможность избежать разделения данных между ядрами, то надо этой возможностью непременно воспользоваться. Но что делать, если такой возможности решительно нет?
Сферическая задача в вакууме
Хорошая новость состоит в том, что устройство, на котором наше ПО будет запущенно, принадлежит полностью нам, можно грузить процессор на 100%, и никто нас за это не поругает, главное — обсчитать как можно больше пакетов и максимально не допустить потерь. Размер данных в UDP-датаграмме не может превышать 512 байт (к примеру).
Архитектура приложения
Из описания задачи сразу становится понятно, что без всех ядер ПК нам тут не обойтись. Первое, что придет в голову более-менее опытному разработчику, — это выделить один поток на прием входящих датаграмм и создать пул потоков для их обработки. Размер пула обычно делают равным количеству доступных ядер у машины.
Так как по условиям задачи нам разрешено максимально использовать ресурсы системы, мы, чтобы упростить код (а следовательно, и снизить число потенциальных ошибок) и уменьшить задержку перед обработкой входящих данных, будем крутить бесконечный цикл, пытающийся вычитать данные из сокета на каждой итерации. Другими словами, ОС не будет усыплять наш поток, если входящих данных нет, и, соответственно, не будет тратить такты процессора на его пробуждение.
Для потоков из пула, которые обрабатывают UDP-пакеты, мы реализуем очередь из этих самых пакетов. Как только главный поток получает датаграмму, он сразу кладет ее в очередь и пытается прочитать следующую порцию данных из сокета. Потоки пула при этом придерживаются той же модели работы, что и main thread. В частности, они будут крутить цикл вычитки пакетов из очереди, не засыпая на ожидании, если очередь пуста.
Теперь надо немного напрячь мозг и подумать, что мы забыли учесть. Поскольку очередь — разделяемая структура данных, то работа с ней связанна с дополнительными расходами времени на синхронизацию. Как мы выяснили немного выше, нам надо максимально избегать разделяемых данных, поскольку это чревато потенциальными ошибками и возникновением «узких мест». К сожалению, полностью отказаться от очереди у нас не выйдет, но вот снизить затраты на синхронизацию вполне можно.
Вместо одной общей очереди для всех мы можем реализовать по отдельной очереди для каждого потока из пула обработки данных. В этом случае вероятность того, что thread, вычитывающий датаграммы, заснет, ожидая, пока завершится операция записи в очередь главным потоком, значительно уменьшается. Очередь для записи может выбираться по простейшему алгоритму, например round-robin.
Конечно, мы могли бы использовать readers-write lock, чтобы обеспечить одновременно чтение из очереди потокам пула, когда не ведется запись. Но проблема в том, что датаграммы будут сыпаться с большой частотой и основная конкуренция за разделяемый ресурс у обработчиков данных окажется не друг с другом, а с главным потоком.
Очередь
Итак, как мы уже поняли, сердце нашего мини-ПО — структура данных, предоставляющая безопасный доступ к пакетам в многопоточной среде. Поскольку софт может полностью утилизировать ресурсы машины, на которой он работает, мы можем сразу при запуске аллоцировать максимальный объем доступной нам памяти для очередей. В этом случае мы избежим затрат на выделение новых кусков памяти ядром ОС для нашего процесса.
Но нам придется решать, как работать дальше, если очередь заполнится. ПО не может просто так взять и упасть, когда доступная память закончится, поэтому у нас есть два варианта. Первый — при добавлении новых элементов в очередь затирать самые старые. В этом случае мы получим классический кольцевой буфер. Второй вариант — возвращать ошибку при попытке добавления элемента в полную очередь. Мы выберем последний вариант, так как в этом случае программа, а следовательно, и пользователь, сможет понять, что мы не справляемся с нагрузкой.
Таким образом, вырисовывается примерный интерфейс класса, который будет имплементировать нашу структуру данных. Назовем его RingQueue. Класс будет иметь как минимум два метода: push и pop. Причем метод push() будет возвращать булев результат, где true обозначает успешное добавление в очередь, а false — очередь полна.
Теперь, когда мы определились с общими принципами, по которым будет работать наш класс, давай подумаем о реализации.
Реализация на основе std::mutex
Первое, что приходит в голову для имплементации класса RingQueue, — это использовать STL vector в качестве хранилища для элементов очереди и STL mutex для защиты данных в многопоточной среде. Ниже представлен код класса RingQueue на основе vector и mutex.
Медлительность Android по сравнению с iOS всегда была мифом, в который почему-то верили миллионы человек. Просто дизайнеры Apple скрыли задержку от запуска приложения до его фактического открытия анимацией, а в Google до этого не додумались. Таким же мифом является склонность Android к засорению и замедлению через какое-то время после начала использования. Дескать, системные кластеры забиваются и уже не могут обеспечивать былой уровень быстродействия. Вот только никто не говорит, что обычно «замедляются» именно старые устройства и только в сравнении с новыми. Но это не значит, что разогнать Android нельзя совсем. Можно.
Разогнать Android можно. Для этого в настройках ОС есть специальные параметры
В Android есть так называемое меню разработчиков. Несмотря на то что оно действительно предназначается для создателей программного обеспечения, рядовые пользователи очень любят включать его, а потом что-то там настраивать и менять, якобы улучшая работу своего устройства. Зачастую это, само собой, совершенно не так. Однако есть несколько надстроек, которые могут позволить хоть немного, но ускорить Android, сделав его чуть отзывчивее, быстрее и податливее. Главное – не переборщить.
Настройки разработчика Android
Для начала нам потребуется активировать меню разработчиков. Если оно у вас уже есть, переходите сразу к третьему пункту инструкции, а если нет – начинайте с первого. Но помните, что активация этих параметров может привести к повышенному ресурсопотреблению и сокращению времени автономной работы.
- Перейдите в «Настройки» и откройте раздел «Об устройстве»;
- Найдите вкладку «Номер сборки» и 10 раз быстро на неё нажмите;
Все необходимые параметры скрыты в меню разработчиков
- Вернитесь назад, а затем перейдите в меню «Для разработчиков»;
- Пролистайте вниз и включите параметр «Ускорить работу GPU»;
- Пролистайте далее и включите пункт «Включить 4x MSAA»;
Активируйте три этих параметра и отключите анимацию
- Затем включите пункт «Отключить аппаратное наложение»;
- В разделе «Скорость анимации» выберите значение x0,5 или x0.
Как ускорить Android
Разогнать Android можно и в играх, и при работе с интерфейсом
Эти три параметра действительно способны разогнать интерфейс вашего смартфона. Вот как это происходит:
Ускорение работы GPU активирует графический ускоритель при отрисовке двумерных элементов. Казалось бы, зачем вообще это нужно? А, между тем, весь интерфейс вашего смартфона и большинство сайтов целиком состоят из 2D-элементов. Активировав ускорение, вы заставите смартфон задействовать графический сопроцессор при обработке всех этих компонентов, а поскольку их в повседневной жизни встречается довольно много, то и прирост быстродействия будет заметен в большинстве задач.
Включение параметра 4x MSAA способно напрямую повлиять на ваше восприятие игр. Независимо от того, двумерная или трёхмерная игра запущена на вашем устройстве, этот пункт повышает контурную детализацию, минимизируя рябь и подёргивания на краях рисованных объектов. В результате создаётся ощущение более плавной обработки видимых графических компонентов. Если хотите, это совсем дешёвый аналог режима 120 Гц, повышающего частоту обновления и делающего картинку более плавной.
Повысить быстродействие смартфона
Отключение аппаратного наложения позволяет задействовать графический сопроцессор при отрисовке компонентов экрана, за счёт чего высвобождается ресурс центрального процессора, и он больше не нагружается в базовых задачах. Может показаться, что этот параметр полностью противоречит первому, но это не совсем так. Вернее, совсем не так. Просто они отвечают за разные процессы.
Смените раскрытые пароли. Что это значит и как реагировать
Изменение скорости анимации – это чисто визуальный, или, если хотите, косметический показатель. В действительности он не повышает скорость запуска приложений, просто он удаляет анимацию, которая по умолчанию заполняет «пустоту» от момента запуска приложения до момента его активации. Но если раньше такая пустота действительно была, и её требовалось чем-то заполнять, то современные смартфоны её практически не допускают. В результате кажется, что приложения из-за анимации запускаются чуть дольше.
Читайте также: