Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Зачем надеятся на оптимизацию при каких-то условиях, если можно руками чётко написать именно то, что хочешь
MoveNext и Current
Вы ссылку смотрели на блог от Microsoft на словах «таким образом»
Это детальное разъяснение к описанию?
Почему нельзя?
Начните с … 1. Хороший алгоритм.
В данном примере метод «PrintInternal» вызывается только из метода «Print», где уже есть проверка. Это значит, что вторая такая же проверка не только не имеет смысла, но и негативно влияет на производительность.
if, а Debug.Assert, который по очевидным причинам не влияет на скорость релизного кода. Более общий случай — все покрыть Code Contracts, а в рерайтере для релизной версии оставить проверки только на public surface.Никаких «foreach» – эта конструкция создаёт экземпляр класса, который реализует интерфейс IEnumerable (чем плохо, описано в принципах 16 и 24).
foreach» — это уже смешно, большая часть кода внутри BCL построена на foreach, особенно в части доступа к данным. А уж весь LINQ без этого просто невозможен.IEnumerable не создается, а используется. Наверное, вы имели в виду IEnumerator? Так нет, это тоже не так, потому что это не обязан быть класс, а может быть и структура (что регулярно используется внутри BCL), и она не обязана реализовывать IEnumerator. Так что оба ваших принципа тут не при чем. Подробности — у Липперта.Существует такой класс как ThreadPool, который является менеджером потоков, и вы с лёгкостью можете делегировать задачи без надобности постоянно создавать новые потоки, что в очередной раз хорошо для производительности. Здесь нельзя не согласиться, если речь идёт о простеньких приложениях, но если требуется серьёзная оптимизация – забудьте! Вам нужен либо свой менеджер асинхронных задач, либо готовые решения.
Представьте что у вас всего 2 ядра у процессора, и размер пула потоков тоже 2. Вы хотите выполнить 3 асинхронные задачи:
- первая считает общее количество бит в большом массиве данных в памяти
- вторая читает данные из сетевого протокола, и пишет на диск
- а третья шифрует небольшое значение с помощью AES-256
Для устранения этих задержек достаточно создать свой класс Stream, который накапливает данные в буфер, а потом ставит его в очередь на запись в отдельном потоке. [...] А для конечного пользователя это будет тот же прозрачный интерфейс класса Stream.
Stream есть сразу два интерфейса — синхронный и асинхронный. И «прозрачно» подменять синхронный асинхронным нельзя, потому что сразу появляются неожиданные ошибки.У структур есть ряд преимуществ перед классами
Поэтому в таких языках, как C++ есть модификатор метода inline, который указывает компилятору, что тело метода необходимо вставить в вызывающий метод. Правда, и в .NET Framework только с версии 4.5 наконец-то можно делать inline методы.
Приведу реальный пример, SqlDataReader в методе GetValue для колонки с типом данных «xml» в конечном итоге вернёт вам String либо XmlReader. А задача состоит в том, чтобы сравнить два значения типа «xml» из двух разных источников. Вроде бы ничего особенного – взял да и сравнил две строки, да ещё можно использовать StringComparison.Oridnal чтоб вообще быстро было.
Организовать список всех ключевых слов проще всего в Hashset. Тогда, чтоб проверить является ли токен ключевым словом, достаточно написать:
HashSet<string> keywords; String tokenText; bool isKeyword = keywords.Contains(tokenText.ToUpperInvariant());
new HashSet<T>(IEqualityComparer<T>) вы не в курсе?есть реальное приложение, которое работает лучше всех
Всё было кропотливо проверено за 3 года работы в этой области.
IEnumerable — тоже? А доказать вы его можете? Для любого использования foreach?А вы лично проверяли то, о чём сейчас написали?
Есть доказательства высокопроизводительных приложений?
Хочу не согласиться про XML. Поскольку речь идет о синхронизации/клонировании/репликации данных, то побайтовое сравнение перед полным сравнением имеет смысл
Мне больше интересно, почему никто на «поверьте, коллизий не будет» не обращает внимания.
Нет. Потому что байтовое представление xml внутри колонки соответствующего типа — это личное дело SQL Server, и оно имеет право отличаться между двумя инстансами.
Но поскольку я вообще всю эту оптимизацию считаю ересью...
Т.е. вы утверждаете, что два одинаковых массива байт могут представлять различные XML данные?
Вы даже не проверяли ни капли сказанного (ну и не будете — зачем это вам надо), а заявляете что это ересь.
Нет, я утверждаю, что два разных массива байт могут представлять одни и те же xml-данные.
хотя любому грамотному программисту известно, что использовать надо вариант со своим компарером
Ну а если массивы разные — то будет сравниваться структура XML — об этом написано в статье. Вы думаете о чём пишете?
Да будет известно грамотному программисту, что перечисление StringComparison содержит 6 значений
StringComparison, если я говорю о компарерах? Я ведь не зря привел сигнатуру конструктора (new HashSet<T>(IEqualityComparer<T>)). Что характерно, там есть и сам алгоритм сравнения объектов (где можно ничего нового не создавать и использовать произвольную математику), и алгоритм расчета хэш-кодов. И именно так реализуются «нестандартные» хэшсеты, а не написанием своего внешнего метода — программист продолжать видеть, что он работает с привычным ему хэшсетом, но логика сравнения внутри оптимизируется.мы получим, что 900, оставшись неизменными, ускорились в 10 раз
Предположу, что потому что версии SQL Server одинаковые, они одинаково обрабатывают входящие данные.
Я тоже полагаю, что сравнение двух массивов байтов перед сравнением XML даст огромный прирост производительности.
Одинаковые, потому что уверен, что они не ставили разные версии для тестирования
думаю, что разные версии серверов не такой забавный челлендж будет, как проблема локальности, когда продукт конкурентов перестанет отставать
уверен, что они не ставили разные версии для тестирования
class String {
public bool Equals(string value)
{
if (this == null)
{
throw new NullReferenceException();
}
return value != null && (object.ReferenceEquals(this, value) || (this.Length == value.Length && string.EqualsHelper(this, value)));
}
}
private unsafe static bool EqualsHelper(string strA, string strB)
{
int i = strA.Length;
if (i != strB.Length)
{
return false;
}
char* ptr = &strA.m_firstChar;
char* ptr2 = &strB.m_firstChar;
bool result;
while (i >= 12)
{
if (*(long*)ptr != *(long*)ptr2)
{
result = false;
}
else
{
if (*(long*)(ptr + (IntPtr)8 / 2) != *(long*)(ptr2 + (IntPtr)8 / 2))
{
result = false;
}
else
{
if (*(long*)(ptr + (IntPtr)16 / 2) == *(long*)(ptr2 + (IntPtr)16 / 2))
{
ptr += (IntPtr)24 / 2;
ptr2 += (IntPtr)24 / 2;
i -= 12;
continue;
}
result = false;
}
}
return result;
}
while (i > 0 && *(int*)ptr == *(int*)ptr2)
{
ptr += (IntPtr)4 / 2;
ptr2 += (IntPtr)4 / 2;
i -= 2;
}
result = (i <= 0);
return result;
}
Equals (в частности, вызывается ли он внутри HashSet) и (б) неверно условие object.ReferenceEquals(this, value).static void Main(string[] args)
{
string[] z =
new string[] { "abc"//воткнуть любой набор строк };
Stopwatch watch1 = new Stopwatch();
Stopwatch watch2 = new Stopwatch();
HashSet<String> str = new HashSet<String>();
HashSet<int> dig = new HashSet<int>();
foreach (var s in z)
{
str.Add(s);
dig.Add(s.GetHashCode());
}
int a = 7;
watch2.Start();
for (int n = 0; n < 10000000; n++)
if (dig.Contains(z[0].GetHashCode()))
a--;
watch2.Stop();
watch1.Start();
for(int n =0;n<10000000;n++)
if (str.Contains(z[0]))
a++;
watch1.Stop();
Console.Write(string.Format("str {0} int {1} a {2}",watch1.Elapsed,watch2.Elapsed,a));
Console.ReadKey();
}
ReferenceEquals("abcd","ab"+"cd");
Подсказать что вернет этот код?
ReferenceEquals(«abcd»,«ab»+«cd»);
int key = 12345; // pre-computed hash code for a string
bool Contains<T>(T key) { // where "T" is "int"
int hashCode = key; // get hash code for "T" type, when it's "int"
int bucketIndex = hashCode % bucketCount;
int itemIndex = buckets[bucketIndex];
@loop:
Item item = itemArray[itemIndex];
if (item.Key == key) // Call operator '==' for 'T' type.
return true;
itemIndex = item.NextItemIndex;
if (itemIndex >= 0)
goto loop;
return false;
}
Имелось в виду при использовании HashSet без своего IEqualityComparer.
А зачем использовать решение, заведомо не подходящее к задаче?
Прочитайте раздел «Специальное дополнение», которое я добавил в конец статьи.
habrahabr.ru/post/165729/#comment_5742425
что совпадений не будет в произвольном тексте
И никогда, никогда не пишите в таком стиле
for (int i = 0; i < a.Count; i++) { int a = a[i]; int b = a[i]; int c = a[i]; }
In the method:
static void Test_SimpleRedundant(int[] a, int i) { k = a[i]; k = k + a[i]; }
bounds-check code is generated for the first instance of “a[i]”, but not the second. In fact, the x86 JIT treats it as a common subexpression, and the first result is re-used.
также избежите лишних проверок границ массива (если таковые имеют место)
Если это будет IList, или что угодно ещё, правило «the first result is re-used» уже может не работать.
int a = a[i];
int b = a[i];
int c = a[i];
int t = a[i]
int a = t;
int b = t;
int c = t;
Arrays implement the IEnumerable interface, which raises a reasonable question: if you enumerate over the elements of an array using C#’s foreach construct, do you get bounds checks? For example:
static int Test_Foreach(int[] ia) { int sum = 0; foreach (int i in ia) { sum += i; } return sum; }
Happily, we do eliminate the bounds checks in this case.
Вы советуете мне как распоряжаться средствами компании?
В начале разработки профилировать ничего не было — ведь кода ещё не было
Вы даже не представляете, сколько действий выполняется, чтобы найти в вашей строке “{0}” и подставить значение в указанном формате.
Скорее всего применительно к (тормозному по умолчанию) .NET
$ free -m
total used free shared buffers cached
Mem: 96730 96418 311 0 71 93120
-/+ buffers/cache: 3227 93502
Swap: 21000 51 20949Когда память понадобится — быстренько освободится.
$ free -m
total used free shared buffers cached
Mem: 64557 64232 324 0 227 57165
-/+ buffers/cache: 6838 57718
Swap: 4093 6 4087$ free -m
total used free shared buffers cached
Mem: 193831 193037 794 0 1783 97128
-/+ buffers/cache: 94125 99706
Swap: 4102 0 4102Итак, практика: 100 IOPS random read дадут вам 3 мб/с, 100 IOPS последовательных чтений дадут 30 мб/с при сравнимой latency. Что это говорит? Что время чтения 1 сектора сильно меньше времени позиционирования головки. У вас другая реальность?
NCQ, кстати, замедляет доступ к диску, зато даёт возможность сделать больше IO операций в секунду. Latency при включении NCQ растёт, т.к. появляется ещё одна очередь запросов.
Но читать с диска сразу мегабайты/гигабайты, как-то кешировать внутри своей системы, следить за этим — это переписывать pagecache операционной системы.
Особенно когда у вас индексы для данных перестают помещаться в память, ну очень нетривиальный код будет.
На неё постоянно сыпется нагрузка в несколько сотен запросов в секунду на чтение и запись, каждая по несколько килобайт.
Коллега, linux kernel hacker, тоже считает, что надо по-максимуму использовать то, что даёт сама операционная система, и что её писали далеко не глупые люди.
Кстати, на счёт вашего выпада в первом посте про десятки гигабайт… У меня обычно по полсотни терабайт данных на сервер. И по несколько сотен тебарайт на кластер.
Кстати, про шедулер: какой шедулер стоит при этом применять и почему, какой при этом будет профит?
Про нюанс «в час» — можете сами посчитать
А в Windows то вы с заканчивающейся памятью на диск писали или в сеть?
В то, что в Windows хреново написан file cache, простите, не верю.
Как хранит данные MongoDB, можно почитать в интернетах.Это переводится как «я не знаю»?
О том, что RA вредит случайному доступу, очевидно и ребёнку, о том, что RA не помогает, я вам написал несколько раз — при линейном чтении от RA никакого толка, потому что линейно всегда читается большими блоками.
You can always use POSIX_FADVISE_RANDOM to disable it, but it's seldom
something that people do. And there are real loads that have random
components to them without being _entirely_ random, so in an optimal world
we should just have heuristics that work well.
Файловая система не предназначена для хранения большого количества мелких экземпляров. Во-первых, оверхеад, во-вторых, скорость низкая, в-третьих, репликация это ужас. Есть несколько ФС, решающих часть проблем (btrfs, например, или gfs), но они нестабильны просто или в плане скорости.Кто вам сказал, что я храню 2 млрд файлов на файловой системе?
Мы храним в MongoDB файлы даже по 500кб. Есть нюансы, конечно, но как решение в целом на данный момент эффективность максимальная.Вы в монгу сможете положить 2млрд записей по 3 кб?
Последовательное чтение используется для парсера лога, например. Естественно, писать надо так, чтобы потом быстро читать. В MongoDB последовательная обработка тоже возможна, но есть нюансы.
Не могу! Вы написали конечный объём в 50 терабайтов, за день это или за год, у меня данных нет. Но это всё равно про Линукс, написал уже два или три раза, что проблемы записи в Windows, используемом автором статьи.Монгу вы под линуксом запускаете или под виндой?
Об этом я написал в самом начале, вера опыт не заменяет. Скопируйте образ BR диска, например, и посмотрите, что будет с системой происходить. Чтобы два раза не вставать, советую смотреть счётчик Available для памяти в Task manager.Вы отключаете writeback и у вас всё равно остаётся много грязных страниц? Видимо, как-то не так отключаете. Вы случаем не во временный файл писали? Если проблема действительно есть — то дайте ссылку, мне сходу ни одной не попалось.
Про отключение writeback занятная теория (отключение WB, сюрприз!, не помогает), есть способ лучше =) O_DIRECT.
Это переводится как «я не знаю»?
в каких задачах из дотнетовского скоупа могут понадобиться такие оптимизации?
Если один алгоритм решает задачу за O(N*logN), а второй за O(logN), то выбор очевиден.
может вызвать только смех. Я уж не говорю о том, что добиться результата можно и двумя умножениями… Можно. Но нужно так: Math.Pow(youVarible, 4); (алгоритм возведения в целую степень за логарифмическое время). Этот кусок кода бросается в глаза почти сразу — плохая реклама.int power4(int v) { int r = 1; r *= v; r *= v; r *= v; r *= v; return r; }
Я уж не говорю о том, что добиться результата можно и двумя умножениями…
Но нужно так: Math.Pow(youVarible, 4);
И не говорите. Я тоже хотел сказать, но потом вспомнил, что результаты будут различными в общем случае.
.NET для такого будет использовать Exp и Ln, ну или что-нибудь, что заведомо медленнее перемножения. Изучили бы лучше сначала как работает Math.Pow, а потом критиковали.
Math.Pow, std::pow или любой другой стандартный power лучше этого смеха.
static double power4(double v, double N)
{
double r = 1;
r *= v;
r *= v;
r *= v;
r *= v;
return r;
}
delegate double PowDelegate(double v, double n);
unsafe static void Main(string[] args)
{
PowDelegate method = Math.Pow;
int timestamp = Environment.TickCount;
for (int i = 1000000; i > 0; i--)
method(10, 4);
int time = Environment.TickCount - timestamp;
Console.WriteLine("Math.Pow: " + time);
method = power4;
timestamp = Environment.TickCount;
for (int i = 1000000; i > 0; i--)
method(10, 4);
time = Environment.TickCount - timestamp;
Console.WriteLine("Custom: " + time);
}
компилятор не выкинул 2-й цикл
И часто вы в своей программе прогоняли тысячи одинаковых возведений в 4-ю степень? :)
Назовите, пожалуйста, этот случай. Интересно.
Вообще-то умножение вещественных чисел не является ассоциативной операцией.
Предложение же возводить целое число в 4-ую степень так:
int power4(int v) { int r = 1; r *= v; r *= v; r *= v; r *= v; return r; }
.NET для такого будет использовать Exp и Ln, ну или что-нибудь, что заведомо медленнее перемножения.
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldc.i4.1
IL_0003: stloc.1
IL_0004: ldarg.0
IL_0005: stloc.s CS$519$0000
IL_0007: ldloc.s CS$519$0000
IL_0009: conv.i
IL_000a: dup
IL_000b: brfalse.s IL_0013
JIT компилятором такое тоже не будет оптимизироваться, я проверял.Которая шла в конце абзаца про (x+3)(x+3).
Я говорил странно про то, что это не оптимизируется на уровне компиляции.Да, на уровне компиляции в IL код практически ничего не оптимизируется, да и не нужно там это. JIT'тер вполне справляется с этой задачей.
Но это простой пример, а если взять пример посложнее…
JIT компилятором такое тоже не будет оптимизироваться, я проверял.
Да, на уровне компиляции в IL код практически ничего не оптимизируется, да и не нужно там это. JIT'тер вполне справляется с этой задачей.
Почему мы там должны себя ограничивать?
Managed c++ даст тот же IL-код. Который точно так же будет скормлен джиттеру. И будет ли этот код эффективнее, чем из C#, всё равно надо будет смотреть.
Просто c++ не будет кроссплатформенным
на вызов потратится значительно больше времени, чем мы выиграем на микрооптимизации
А насчет «экономически нецелесообразно» — тут уж зависит от задачи. И от имеющихся ресурсов и их распределения.
Как минимум, он даст IL не хуже, чем описанный в статье, однако при этом не будет насилием над C#.
В случае с .net это не великая проблема.
Вот именно. И я пока ни разу не видел задачи и матрицы ресурсов, при которых подход статьи был бы оправдан.
Может оказаться хуже, если компилятору managed c++ уделяется меньше внимания, чем C#.
После полного отказа от unmanaged кода в проекте разрабатывать и поддерживать его стало гораздо легче.
Как насчет embedded .net кода на устройстве, на котором нужно экономить потребляемые процессором ватты, но при этом не терять производительности?
Переписывать всю программу на unmanged код будет дольше, чем потихоньку оптимизировать кусок за куском и продавать новые улучшенные версии устройства
Интересно, под управлением чего это устройство, и почему для него до сих пор не сделали свой собственный jit-компилятор.
Не понятно, почему здесь нет пункта «переписывать куски программы на unmanaged-код».
Все тесты я проводил на своём рабочем компьютере с вот такой конфигурацией:[...] Microsoft SQL Server 2008 R2 Developer Edition 64-bit
Donald Knuth, author of the classic four-volume series The Art of Computer Programming, famously said “premature optimization is the root of all evil.“. I think however that it is more instructive to read that quotation with more context:
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.
Which is of course what I have echoed in my numerous performance rants over the years: don’t waste your valuable time making risky changes to the 97% of the code that isn’t the slowest thing and that no customer will ever notice. Use a profiler, find the slowest 3%, and spend your optimization budget on that.
Вообще глобально любая оптимизация — это повышение скорости работы приложения в ущерб затратам на его дальнейшую поддержку. Чем больше тюнинга, тем хуже код.
Проблема статьи в том, что автор начал оптимизировать приложение слишком рано
низкоуровневой оптимизации, которые на сегодняшний день дают небольшой прирост производительности
1) HashSet + ToUpperInvariant
2) HashSet + StringComparer.OrdinalIgnoreCase в качесте IEqualityComparer
3) Оптимизированный код — рабочий, а не демонстрационный как в статье.
C ToUpperInvariant я действительно погорячился, представляя его в статье — метод действительно оказался очень отвратным, но это можно было проверить только опытным путём.
И это в очередной раз подтверждает вредность создания новых объектов и неиспользования слияния алгоритмов, которое с большой вероятностью есть в недрах .NET Framework (или Windows).
Думайте глобальнее — если функция экономит 400мс в час, то это будет экономия одного часа в год.
насколько часто прийдётся менять функцию проверки токена на ключевое слово?
Что выпытаетесь доказать?
Вы так говорите, как будто я пропагандирую такой способ разработки.
Я нигде в статье не даю советы как нужно разрабатывать приложения, а вы мне пытаетесь рассказать инное.
Вы понимаете что проект, на котором был поставлен эксперимент такой разработки, просто демонстрирует чего можно добиться?
И как это отличается от стандартного подхода «сделать, найти bottleneck, оптимизировать», который представлен конкурентным решением?
Почему вы отвергаете такого рода эксперименты и рассказваете мне как «надо» делать?
для достижения максимальной производительности приложений, которые этого требуют
для достижения максимальной производительности приложений, которые этого требуют
Стоимость разработки — около 1700 человеко-часов
Мы потратили в почти 10 раз меньше времени чтобы это всё переделать с чистого листа и довести до предела. Как вы оцените такие цифры?
Потому что основные бизнес-алгоритмы, насколько я понимаю, уже были разработаны.
Вопрос в том, получили ли вы соответствующий прирост прибыли, и не выгоднее ли было вложить эти ресурсы в разработку новой функциональности.
Решение, которое работает на бою в результате всех махинаций, пусть даже и идеологически неверно написанное, разве это плохо?
Конкретно это приложение развивать не вам и от того что вы тут пишете «плохо» написанный код не перепишется.
Люди на собеседовании будут уведомлены о предстоящей работе, кому не нравится, тот уйдет.
Самую большую пользу, как мне кажется, приносит работа с такими проектами, которые на границе разумного.
Зато есть надежда, что кто-то подумает, прежде чем порождать еще одно такое же.
Пользу кому? Вам или заказчику?
Заказчику-то какое дело, как там все внутри? Работает быстро и качественно
Мне кажется, что заказчику нельзя сказать «у нас тут все сложно, поэтому вы платите больше». Есть договора и все такое.
Ну тут можно долго считать, что лучше — много заказчиков из-за быстрого продукта или дешевая поддержка
Статью будете писать или продолжите длинные споры в комментариях?
Идеологически — это про любой интерпретируемый/управляемый код во всех его вариациях
лишние сервисы Microsoft .NET Framework NGEN и ASP.NET State Service
Был бы таким дотнет — вопросов к нему бы не было.
В итоге в системе образуется свалка из 4х версий .net framework, каждая из которых поставлена полностью.
Предвижу совет использовать SSD или наушники :)
Начните с … 1. Хороший алгоритм.
Нет, начинать надо с того, что определить, где именно у вас боттлнек (и есть ли он вообще).
«Давайте сделаем в лоб не задумываясь, а потом найдём проблему и придумаем для неё алгоритм решения»
проблему искать не надо, она включает в себя всю программу
Написать сразу-правильную программу довольно рискованное мероприятие
17. Освобождение памяти.
…
Здесь можно вывести для себя очень простое правило – как только объект перестаёт быть нужным, обнулите ссылку на него.
null проставлять.отписаться — не архитектурная пролема
Это косвенно ведёт к установке в NULL
Архитектурная проблема — это то, что вы теряетесь в графе зависимостей между классами.
Вообще-то, нет. Это ведет к убиранию делегата из списка.
Архитектура может быть замечательной и понятной, но это только на диаграмме, а вот когда смотришь в код, то видишь офигенное кол-во классов, и связи нужно проставлять не между компонентами архитектуры, а между классами, которые её реализуют, потому что .NET оперирует имеенно объектами для сборки мусора, а не какой-то там диаграммой архитектуры.
А при сборке мусора массив будет «удалён», при этом все объекты, на которые он ссыллася станут отвязанными от него, что можно считать установкой в NULL на уровне CLR.
Не пробовали уменьшать количество связей между классами?
Можно считать. А можно — не считать.
Думаю, про вредность Boxing / Unboxing в этом примере не стоит рассказывать, и надеюсь, вы догадываетесь как это исправить.
Предельная производительность: C#