Как стать автором
Обновить
0
0
Павел @FANAT1242

.NET-разработчик

Отправить сообщение

FYI: uTorrent хранит в "resume.dat" список торрентов и данные по ним (статистику, подключенных пиров и т.п.). Файл периодически перезаписывается. В uTorrent 3.0+ можно настроить интервал сохранения "resume.dat" -- опцией bt.save_resume_rate.

Как писали выше, любой метод в C# теоретически может кинуть любое исключение, поэтому дефолтные обработчики исключений обычно присутствуют, просто чтобы приложение не завалилось от какого-нибудь NullReferenceException. А дальше уже изыски в зависимости от требований — а чего, собственно, вам нужно от ошибок/исключений?


Расскажу про подход, который используем мы. Он сложился эмпирически.


У нас обычная система с клиент-серверной архитектурой. На сервере все ошибочные ситуации разделены на три типа (на самом деле чуть больше, но опишу упрощенно):


  1. Ошибки, допустимые бизнес-логикой. То есть систему так сконфигурировали, что при обработке запроса возникает ошибка (например, пользователя выключили в настройках, а он пробует залогиниться).
  2. Ошибки из-за неправильного внешнего запроса. То есть снаружи (от клиента) прилетел запрос, который не должен прилетать с точки зрения бизнес-логики. Например, вызов GetUser(userId) с передачей несуществующего userId. Или запрос к HTTP API в непредусмотренном формате. Или обращение к чему-то, что запрещено правами доступа.
  3. Остальные ошибки — например, ошибка выполнения запроса к БД (отсутствие соединения, таймаут). NullReferenceException из-за косяка программиста. StackOverflowException из-за избыточного чтения stackoverflow.com.

Нетрудно заметить, что эти типы ошибок определяют сторону, ответственную за возникновение ошибки:


  1. Ошибки, допустимые бизнес-логикой, возникают из-за пользователей — это они так настроили систему.
  2. Ошибки из-за неправильных запросов возникают по вине клиентских приложений.
  3. Остальные ошибки возникают по вине сервера.

Соответственно, в логах сервера:


  1. Ошибки, допустимые бизнес-логикой, пишутся с уровнем INFO и без трассировки стека.
  2. Ошибки неправильных запросов пишутся с уровнем WARN, трассировка стека по желанию самого исключения (флажок withStackTrace в конструкторе), но обычно трассировки нет.
  3. Остальные ошибки пишутся с уровнем ERROR с трассировкой стека.

Точка зрения сервера: на ошибки 1-го и 2-го типа он повлиять не может, а ошибки 3-го типа должен минимизировать, это его зона ответственности.
Точка зрения клиентского приложения: на ошибки 1-го и 3-го типа (которые возвращает сервер), оно повлиять не может, а ошибки 2-го типа должно минимизировать, это его зона ответственности.


У нас далеко не хайлоад, и профайлер не показывает затраты на выбрасывание исключений как критичные. Пока что :)


P.S. Чем больше копится опыт, тем больше понимаю, что главные проблемы в программировании — не технические, а проблемы бизнеса, которые ваш код должен решать.

Первое правило оптимизации производительности — измерь выгоду.
Второе правило оптимизации производительности — соотнеси плюсы (выгоду) с минусами (в виде усложнения сопровождения кода и т.п.).

По поводу ToList() vs ToArray(): например, если получать данные из БД в коллекцию, то особой разницы между ними нет. Ведь, условно, 99,9% времени тратится на получение данных, а не на складывание их в коллекцию в памяти :)

Ну а если кто-то дошел до того, что разница между ToArray() и ToList() действительно существенна, лучше вообще не использовать Linq и получить еще больший прирост производительности. Потому как вызовы Where(...), Select(...) и т.п.:
— создают структуру-итератор, которая используется через интерфейс, т.е. упаковывается;
— принимают делегаты, которые сами являются объектами, т.е. размещаются в куче, и их вызов происходит медленнее, чем вызов вашего кода непосредственно без делегата.
Вдобавок, foreach по IEnumerable IL-компилируется в вызовы GetEnumerator(), MoveNext() и т.п., без оптимизаций по конкретному типу коллекции. Для сравнения, foreach по массиву T[] компилируется в куда более быстрый код.

Из подходов, относящихся непосредственно к C# и .NET, которые помогали (по опыту, в порядке выгоды):

1. class vs struct
Если работаете с многими тысячами/миллионами объектов в памяти, и вам не надо ссылаться на одни и те же объекты в разных местах, старайтесь использовать структуры вместо классов. Получите уменьшение потребления памяти и ускорение работы.
Ведь экземпляры классов (объекты) требуют выделения памяти и находятся в управляемой куче. Экземпляры структур не требуют отдельного выделения памяти и находятся там, где объявлены (в стек-фрейме метода, внутри экземпляра класса и т.п.). Так, в массиве объектов хранятся только ссылки на эти объекты, а сами объекты находятся в куче и могут быть разбросаны по разным местам памяти. В массиве структур данные хранятся компактно — в итоге, меньшее использование памяти, более высокая локальность, меньше напрягов для сборщика мусора.
Ньюанс: если ваша структура реализует интерфейс, и вы объявите массив интерфейсов, положив туда структуры, то каждый элемент массива упакуется, и вся выгода от использования структуры сойдет на нет.

2. Методы, возвращающие итераторы (через yield return)
Они компилируются в довольно объемный и относительно медленный итератор. При вызове происходят обращения к Thread.CurrentThread.ManagedThreadId для обеспечения правильной работы итератора в разных потоках. У нас была ситуация, когда уход от yield return уменьшил время выполнения некоторых расчетов на 14%.

3. Task.Run(...)
Класс Task очень удобен для асинхронного программирования. Но если требуется очень часто выполнять метод в пуле потоков, то банальные вызовы ThreadPool.QueueUserWorkItem(...) и ThreadPool.UnsafeQueueUserWorkItem(...) решат вашу задачу с чуть меньшими издержками.

4. Вируальные вызовы (callvirt)
Вызов виртуального метода включает в себя:
— переход к таблице виртуальных методов в типе объекта
— поиск нужного метода в этой таблице
— собственно вызов найденного метода
Поэтому в местах, критичных к производительности, проверяйте наличие виртуальных вызовов, если все остальное уже соптимизировали :)
Ньюанс: если в классе вы неявно реализуете метод интерфейса, это метод неявно помечается как виртуальный.

P.S. А вообще, самые эффективные оптимизации — на уровне архитектуры приложения и используемых алгоритмов.
При указанном способе параллельной записи в файл «каши» не будет и в .NET, т.к. все сообщения равной длины. Когда два потока одновременно дозаписывают данные в файл, данные будут просто записаны в один и тот же участок файла. Т.е. один поток перезапишет данные другого потока.

Можете попробовать посчитать общее количество записанных строк в файле. Если Node.JS выполняет какую-то I/O-синхронизацию, то 10 потоков, записывающих по 1000 строк, дадут ровно 10000 строк в файле. В противном случае строк будет меньше.

Или попробуйте в разных потоках писать сообщения разной длины.

Информация

В рейтинге
Не участвует
Откуда
Тюмень, Тюменская обл. и Ханты-Мансийский АО, Россия
Дата рождения
Зарегистрирован
Активность