Наконец, поставил бету новой студии. Вдохновленный рассказами о чудесах оптимизатора C#, решил поиграться с C# generics... В общем, почему-то на моих тестах тормозит в 2-3 раза по сравнению с аналогичным кодом на C++. Я подозреваю собственные кривые руки, т.к. с C# знаком весьма посредственно. Помогите, пожалуйста, разобраться, что я не так делаю.
Мне было интересно сравнить использование аналогичных техник в C# и C++ для сортировки структуры—обертки над int. Чтобы не мучиться с алгоритмом, взял из первого попавшегося сообщения
Я пробовал два варианта: с делегатом, как было в исходном сообщении, плюс, подозревая, что тормозят как раз делегаты, еще один вариант с прямым использованием члена CompareTo сортируемой структуры. По сравнению с boost::function делегаты отстают почти в 3 раза, прямой вызов CompareTo по сравнению с аналогичным кодом на C++ — почти в 2
struct IntWrapper : IComparable<IntWrapper>
{
public IntWrapper(int value)
{
value_ = value;
}
public static implicit operator int(IntWrapper w)
{
return w.value_;
}
public static IntWrapper operator -(IntWrapper l, IntWrapper r)
{
return new IntWrapper(l.value_ - r.value_);
}
public int CompareTo(IntWrapper r)
{
return this - r;
}
public bool Equals(IntWrapper r)
{
return CompareTo(r) == 0;
}
private int value_;
};
Тесты:
class ComparableTest<T> : Test<T> where T : IComparable<T>
{
public ComparableTest(CreateElement create_element)
: base( create_element )
{
}
protected override void Sort(T[] array)
{
Sort(array, 0, array.Length - 1);
}
private static void Sort(T[] array, int left, int right)
{
int i = left;
int j = right;
T center = array[(left + right) / 2];
while (i <= j)
{
while (array[i].CompareTo(center) < 0)
i++;
while (array[j].CompareTo(center) > 0)
j--;
if (i <= j)
{
T x = array[i];
array[i] = array[j];
array[j] = x;
i++;
j--;
}
}
if (left < j)
Sort(array, left, j);
if (right > i)
Sort(array, i, right);
}
protected override int Search(T[] array, T value)
{
return BinarySearch(array, 0, array.Length, value);
}
private static int BinarySearch(T[] array, int lo, int hi, T value)
{
while (lo <= hi)
{
int i = (lo + hi) / 2;
int cmpResult = array[i].CompareTo(value);
if (cmpResult == 0)
return i;
else if (cmpResult < 0)
lo = i + 1;
else
hi = i - 1;
}
return ~lo;
}
};
class DelegateTest<T> : Test<T>
{
public delegate int CompareElement(T x, T y);
public DelegateTest(CreateElement create_element, CompareElement compare_element)
: base( create_element )
{
compare_element_ = compare_element;
}
protected override void Sort(T[] array)
{
Sort(array, 0, array.Length - 1, compare_element_);
}
private static void Sort(T[] array, int left, int right, CompareElement compare)
{
int i = left;
int j = right;
T center = array[(left + right) / 2];
while (i <= j)
{
while (compare(array[i], center) < 0)
i++;
while (compare(array[j], center) > 0)
j--;
if (i <= j)
{
T x = array[i];
array[i] = array[j];
array[j] = x;
i++;
j--;
}
}
if (left < j)
Sort(array, left, j, compare);
if (right > i)
Sort(array, i, right, compare);
}
protected override int Search(T[] array, T value)
{
return BinarySearch(array, 0, array.Length, value, compare_element_);
}
private static int BinarySearch(T[] array, int lo, int hi, T value, CompareElement compare)
{
while (lo <= hi)
{
int i = (lo + hi) / 2;
int cmpResult = compare(array[i], value);
if (cmpResult == 0)
return i;
else if (cmpResult < 0)
lo = i + 1;
else
hi = i - 1;
}
return ~lo;
}
CompareElement compare_element_;
};
Общая база для тестов:
abstract class Test<T>
{
public delegate T CreateElement(int value);
protected Test(CreateElement create_element)
{
create_element_ = create_element;
}
public void Run(string fileName)
{
T[] array = CreateArray(fileName);
T value = array[0];
Console.WriteLine("Sort duration: " + Measure( delegate() { Sort(array); } ) );
int index = Search(array, value);
Console.WriteLine("Index: " + index);
}
protected delegate void Action();
private double Measure(Action action)
{
DateTime start = DateTime.Now;
action();
DateTime end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
protected abstract void Sort(T[] array);
protected abstract int Search(T[] array, T element);
private T[] CreateArray(string fileName)
{
ArrayList array = new ArrayList();
BinaryReader reader =
new BinaryReader(File.Open(fileName, FileMode.Open));
try
{
while (true)
{
int value = reader.ReadInt32();
array.Add(create_element_(value));
}
}
catch (EndOfStreamException)
{
// ignore
}
finally
{
reader.Close();
}
return (T[])array.ToArray(typeof(T));
}
private CreateElement create_element_;
};
Запуск тестов:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Delegate...");
TestDelegate(args[0]);
Console.WriteLine("CompareTo...");
TestComparable(args[0]);
}
private static void TestDelegate(string fileName)
{
Test<IntWrapper>.CreateElement create_element_delegate =
delegate(int value) { return new IntWrapper(value); };
DelegateTest<IntWrapper>.CompareElement compare_element_delegate =
delegate(IntWrapper x, IntWrapper y) { return x - y; };
DelegateTest<IntWrapper> test =
new DelegateTest<IntWrapper>(
create_element_delegate,
compare_element_delegate
);
test.Run(fileName);
}
private static void TestComparable(string fileName)
{
Test<IntWrapper>.CreateElement create_element_delegate =
delegate(int value) { return new IntWrapper(value); };
ComparableTest<IntWrapper> test =
new ComparableTest<IntWrapper>(create_element_delegate);
test.Run(fileName);
}
}
(*) Правда, оказалось, что то ли сортировка там не вполне сортирует, то ли поиск не вполне ищет, то ли и то, и другое, но это нам сейчас совершенно неважно, поэтому прошу по этому поводу не пинать: в любом случае, алгоритмы и в C#, и в C++ одни и те же, так что на время, что нас и интересует, это влиять не должно.
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Hello, "Павел Кузнецов"
> Я пробовал два варианта: с делегатом, как было в исходном сообщении, плюс, подозревая, что тормозят как раз делегаты, еще один вариант с прямым использованием члена CompareTo сортируемой структуры. По сравнению с boost::function делегаты отстают почти в 3 раза, прямой вызов CompareTo по сравнению с аналогичным кодом на C++ — почти в 2 >
У меня использование ngen делает из 250 единиц 220 (для CompareTo). Также, если сделать специализированную для IntWrapper версию с использованием указателей, то получается 190 ед (все тотже CompareTo).
Вот такая оптимизиция...
Posted via RSDN NNTP Server 1.9 alpha
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
KISS. В managed коде почти любая абстракция будет тебе стоить. Чем меньше абстракций, тем быстрее. Delegates и высокопроизводительный код — вещи вообще несовместимые. Даже виртуальный вызов быстрее, чем delegate. Imho лучше использовать IComparer, чем IComparable, который к тому-же можно объявить struct, чтобы вызовы Compare() были невиртуальными.
Измерения на 19MB файле (у меня память медленная по сравнению с процессором, поэтому результаты не в одинаковой пропорции с твоими):
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
class ComparableTest<T, Comp> : Test<T>
where Comp
: IComparer< T >
, new()
{
public ComparableTest(CreateElement create_element)
: base( create_element )
{
comparer_ = new Comp();
}
protected override void Sort(T[] array)
{
Sort(array, 0, array.Length - 1);
}
private void Sort(T[] array, int left, int right)
{
int i = left;
int j = right;
T center = array[(left + right) / 2];
while (i <= j)
{
while (comparer_.Compare( array[i], center ) < 0)
i++;
while (comparer_.Compare( array[j], center ) > 0)
j--;
if (i <= j)
{
T x = array[i];
array[i] = array[j];
array[j] = x;
i++;
j--;
}
}
if (left < j)
Sort(array, left, j);
if (right > i)
Sort(array, i, right);
}
protected override int Search(T[] array, T value)
{
return BinarySearch(array, 0, array.Length, value);
}
private int BinarySearch(T[] array, int lo, int hi, T value)
{
while (lo <= hi)
{
int i = (lo + hi) / 2;
int cmpResult = comparer_.Compare( array[i], value );
if (cmpResult == 0)
return i;
else if (cmpResult < 0)
lo = i + 1;
else
hi = i - 1;
}
return ~lo;
}
// private
Comp comparer_;
};
abstract class Test<T>
{
public delegate T CreateElement(int value);
protected Test(CreateElement create_element)
{
create_element_ = create_element;
}
public void Run(string fileName)
{
T[] array = CreateArray(fileName);
T value = array[0];
Console.WriteLine("Sort duration: " + Measure( delegate() { Sort(array); } ) );
int index = Search(array, value);
Console.WriteLine("Index: " + index);
}
protected delegate void Action();
private double Measure(Action action)
{
DateTime start = DateTime.Now;
action();
DateTime end = DateTime.Now;
return (end - start).TotalMilliseconds;
}
protected abstract void Sort(T[] array);
protected abstract int Search(T[] array, T element);
private T[] CreateArray(string fileName)
{
ArrayList array = new ArrayList();
BinaryReader reader =
new BinaryReader(File.Open(fileName, FileMode.Open));
try
{
while (true)
{
int value = reader.ReadInt32();
array.Add(create_element_(value));
}
}
catch (EndOfStreamException)
{
// ignore
}
finally
{
reader.Close();
}
return (T[])array.ToArray(typeof(T));
}
private CreateElement create_element_;
};
struct IntComparer : IComparer< int >
{
public int Compare( int a, int b )
{
return a - b; // watch for integer overflow!
}
public bool Equals( int a, int b )
{
return a == b;
}
public int GetHashCode( int obj )
{
return obj;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Compare...");
TestComparable(args[0]);
}
private static void TestComparable(string fileName)
{
Test<int>.CreateElement create_element_delegate =
delegate(int value) { return value; };
ComparableTest<int, IntComparer> test =
new ComparableTest<int, IntComparer>(create_element_delegate);
test.Run(fileName);
}
}
ROFL
> В managed коде почти любая абстракция будет тебе стоить. Чем меньше абстракций, тем быстрее.
Да уж... Особенно "порадовало", что из-за ограничений generics приходится дублировать код даже для написания самих тестов
Собственно, именно плату за повышение уровня абстракции я и хотел себе представить. Пожалуй, можно будет адаптировать на C# Abstraction penalty benchmark Степанова, если захочется увидеть более полную картину.
> Imho лучше использовать IComparer, чем IComparable, который к тому-же можно объявить struct, чтобы вызовы Compare() были невиртуальными.
Спасибо за вариант. Что интересно, твой вариант является самым быстрым для int, а для IntWrapper, по крайней мере, на моей машине, самым быстрым остается CompareTo. У меня серьезное ощущение, что дело не только в IComparer вместо IComparable, а еще и в int вместо IntWrapper:
struct IntWrapperComparer : IComparer<IntWrapper>
{
public int Compare(IntWrapper a, IntWrapper b)
{
return a - b; // watch for integer overflow!
}
public bool Equals(IntWrapper a, IntWrapper b)
{
return a == b;
}
public int GetHashCode(IntWrapper obj)
{
return obj;
}
};
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Delegate...");
TestDelegate(args[0]);
Console.WriteLine("CompareTo...");
TestComparable(args[0]);
Console.WriteLine("Comparator...");
TestComparator(args[0]);
Console.WriteLine("Delegate (int)...");
TestIntDelegate(args[0]);
Console.WriteLine("CompareTo (int)...");
TestIntComparable(args[0]);
Console.WriteLine("Comparator (int)...");
TestIntComparator(args[0]);
}
private static void TestDelegate(string fileName)
{
Test<IntWrapper>.CreateElement create_element_delegate =
delegate(int value) { return new IntWrapper(value); };
DelegateTest<IntWrapper>.CompareElement compare_element_delegate =
delegate(IntWrapper x, IntWrapper y) { return x - y; };
DelegateTest<IntWrapper> test =
new DelegateTest<IntWrapper>(
create_element_delegate,
compare_element_delegate
);
test.Run(fileName);
}
private static void TestComparable(string fileName)
{
Test<IntWrapper>.CreateElement create_element_delegate =
delegate(int value) { return new IntWrapper(value); };
ComparableTest<IntWrapper> test =
new ComparableTest<IntWrapper>(create_element_delegate);
test.Run(fileName);
}
private static void TestComparator(string fileName)
{
Test<IntWrapper>.CreateElement create_element_delegate =
delegate(int value) { return new IntWrapper(value); };
ComparatorTest<IntWrapper, IntWrapperComparer> test =
new ComparatorTest<IntWrapper, IntWrapperComparer>(create_element_delegate);
test.Run(fileName);
}
private static void TestIntDelegate(string fileName)
{
Test<int>.CreateElement create_element_delegate =
delegate(int value) { return value; };
DelegateTest<int>.CompareElement compare_element_delegate =
delegate(int x, int y) { return x - y; };
DelegateTest<int> test =
new DelegateTest<int>(
create_element_delegate,
compare_element_delegate
);
test.Run(fileName);
}
private static void TestIntComparable(string fileName)
{
Test<int>.CreateElement create_element_delegate =
delegate(int value) { return value; };
ComparableTest<int> test =
new ComparableTest<int>(create_element_delegate);
test.Run(fileName);
}
private static void TestIntComparator(string fileName)
{
Test<int>.CreateElement create_element_delegate =
delegate(int value) { return value; };
ComparatorTest<int, IntComparer> test =
new ComparatorTest<int, IntComparer>(create_element_delegate);
test.Run(fileName);
}
}
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Мне было интересно сравнить использование аналогичных техник в C# и C++ для сортировки структуры—обертки над int. Чтобы не мучиться с алгоритмом, взял из первого попавшегося сообщения
Serginio1,
> ПК> Мне было интересно сравнить использование аналогичных техник в C# и C++ для сортировки структуры—обертки над int. Чтобы не мучиться с алгоритмом, взял из первого попавшегося сообщения
, чем вариант с вызовом CompareTo.
Достаточно странное поведение. Но здесь может срабатывать GC поэтому желательно делать по несколько тестов.
Разница бывает очень ощютима. А разницы межды int и IntWrapper вроде нет, или инлайнит не совсем правильно.
В любом случае нужно еще проверить. Как бы там не было это только бэтта. Но ситуация с дженериками постоянно исправляется к лучшему.
и солнце б утром не вставало, когда бы не было меня
, чем вариант с вызовом CompareTo.
> Достаточно странное поведение. Но здесь может срабатывать GC поэтому желательно делать по несколько тестов.
Несколько запусков? Так и делалось. Результаты варьируются в достаточно небольших пределах. Во всяком случае разброс не влияет на "рейтинг" результатов относительно друг друга.
> А разницы межды int и IntWrapper вроде нет, или инлайнит не совсем правильно.
> В любом случае нужно еще проверить. Как бы там не было это только бэтта. Но ситуация с дженериками постоянно исправляется к лучшему.
Честно говоря, больше всего напрягла необходимость дублировать код для каждого из тестов из-за отсутствия С++-подобных шаблонов без ограничений, присущих generics. Думаю, может, переписать тест на C++/CLI? Заодно увидим, может, там оптимизация удачнее
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
ПК>Не совсем понял... В сообщении по ссылке четыре варианта, и все относятся к записи
Лучше исползовать не
while (true)
{
int value = reader.ReadInt32();
array.Add(create_element_(value));
}
А считывать в буффер byte[] , из которого через BlockCopy копировать в массив. Вопервых нетипизированный ArrayList это боксинг и унбоксинг,затраты памяти фрагментация памяти.
При копировании через буффер этих затрат нет. А заодно и сравнишь размеры твоего Интврайпера.
И соответственно GC срабатывать будет реже.
и солнце б утром не вставало, когда бы не было меня
Serginio1,
> считывать в буффер byte[] , из которого через BlockCopy копировать в массив.
А можно ли через BlockCopy копировать побайтно в массив IntWrapper[]? Что-то я не нашел гарантий работоспособности этого способа в стандарте C#. По первому впечатлению попахивает использованием для аналогичных целей memcpy в C++ со всеми вытекающими.
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Serginio1,
>> считывать в буффер byte[] , из которого через BlockCopy копировать в массив.
ПК>А можно ли через BlockCopy копировать побайтно в массив IntWrapper[]? Что-то я не нашел гарантий работоспособности этого способа в стандарте C#. По первому впечатлению попахивает использованием для аналогичных целей memcpy в C++ со всеми вытекающими.
Можно попробовать через Marshal.Copy. Например:
// для примера данные инициализируются только на стеке
IntWrapper* array = stackalloc IntWrapper[arraySize];
Marshal.Copy(streamAsByteArray/*данные из потока*/, 0, new IntPtr(array), sizeof(int));
А вообще пора уже избавляться от unmanaged подхода. Только сериализация и десериализация данных.
Hello, "Павел Кузнецов" > > А можно ли через BlockCopy копировать побайтно в массив IntWrapper[]? Что-то я не нашел гарантий работоспособности этого способа в стандарте C#. По первому впечатлению попахивает использованием для аналогичных целей memcpy в C++ со всеми вытекающими.
Тут в отличии от memcpy гарантирутеся, что нельзя "пройтись" по объектным ссылкам. т.е. если это структура полями которой выступают примитивные типы, то по большому счету не так страшно, что туда запишется... приложение это не разрушит.
Posted via RSDN NNTP Server 1.9
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Hello, "Павел Кузнецов" > >> Достаточно странное поведение. Но здесь может срабатывать GC поэтому желательно делать по несколько тестов. > > Несколько запусков? Так и делалось. Результаты варьируются в достаточно небольших пределах. Во всяком случае разброс не влияет на "рейтинг" результатов относительно друг друга. >
Можно делать не несколько запусков, а несколько последовательных проходов без выгрузки приложения и взятия среднего (в идеале несколько первых проходов можно считать загрузовчными и игнорировать). Что-же касается влияния GC, то пытаться подстроиться под то, что-бы он не вызывался — несколько странно... В реальном приложении он-же вызываться будет
Да и для подобного теста его влияние должно быть минимальным — вот, если делать на IntWrapper, а ObjectWrapper ...
> Честно говоря, больше всего напрягла необходимость дублировать код для каждого из тестов из-за отсутствия С++-подобных шаблонов без ограничений, присущих generics. Думаю, может, переписать тест на C++/CLI? Заодно увидим, может, там оптимизация удачнее
С этого и надо было начитать Вроде как даже в VS2003 компилятор для MC++ оптимизировать умеет...
Posted via RSDN NNTP Server 1.9
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
TK,
> если это структура полями которой выступают примитивные типы, то по большому счету не так страшно, что туда запишется... приложение это не разрушит.
Последнее понятно. Вопрос, будет ли запись корректной? В данном случае это, конечно, побоку, но тем не менее? Как-то не нравится "выжимать" такты за счет использования конструкций, корректность работы которых не гарантирована
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Hello, "Павел Кузнецов" > >> если это структура полями которой выступают примитивные типы, то по большому счету не так страшно, что туда запишется... приложение это не разрушит. > > Последнее понятно. Вопрос, будет ли запись корректной? В данном случае это, конечно, побоку, но тем не менее? Как-то не нравится "выжимать" такты за счет использования конструкций, корректность работы которых не гарантирована
Тут есть некоторая двойственность. Думаю, что сама запись корректна, но вот то, что операция sizeof является unsafe это смущает
Posted via RSDN NNTP Server 1.9
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
TK,
>> Вопрос, будет ли запись корректной? В данном случае это, конечно, побоку, но тем не менее? Как-то не нравится "выжимать" такты за счет использования конструкций, корректность работы которых не гарантирована
> Тут есть некоторая двойственность. Думаю, что сама запись корректна, но вот то, что операция sizeof является unsafe это смущает
Ага, спасибо. Просто из чтения стандарта у меня сложилось ощущение, что даже не гарантируется, что sizeof(int) == sizeof(IntWrapper):
For all other types < не из перечисленного чуть выше набора >, the result of the sizeof operator is implementation-defined and is classified as a value, not a constant.
<...>
The order in which members are packed into a struct is unspecified.
For alignment purposes, there can be unnamed padding at the beginning of a struct, within a struct, and at the end of the struct. The contents of the bits used as padding are indeterminate.
When applied to an operand that has struct type, the result is the total number of bytes in a variable of that type, including any padding.
Posted via RSDN NNTP Server 1.9
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
ПК> ПК>(*) Правда, оказалось, что то ли сортировка там не вполне сортирует, то ли поиск не вполне ищет, то ли
Прогаммисты хреновые...
ПК> и то, и другое, но это нам сейчас совершенно неважно
А, ну, да. У теста провекрки на вшивость не проходят, но это не важно... Разве это может повлиять на результат?
Обрати внимание на приведенные мною фрагменты твоего теста. Заметил выделенное жирным? Вот из-за них и тесты глючат. У тебя в массивах явно попадаются отрицательные велечины и когда ты большую отрицательную величину вычиташь из большой положительной, то происходит переполнение, которое без дополнительной обработки (которую только на ассемблере сделать можно) приводит к неверному результату сравнения. После этого никакие результаты уже не интересны, так как сортировка просходит абы как.
Я вот заменил в твоем тесте:
public int CompareTo(IntWrapper r)
{
return this - r;
}
на:
public int CompareTo(IntWrapper r)
{
return value_ - r.value_;
}
и получил удвоение производительности этого глюкалова. Вот только что я измеряю хоть убей сказать не могу.
Убедиться в моей правоте можно включив проверку переполения. Для Шарпа это делается в "Project\Properties+Build+Advanced".
или заменять делегаты и интерфейсы на содержащие метод Great и использовать в алгоритме сортировки его.
ПК>, поэтому прошу по этому поводу не пинать:
ПК> в любом случае, алгоритмы и в C#, и в C++ одни и те же, так что на время, что нас и интересует, это влиять не должно.
Ага. И главное полная гарантия, что переполнение обрабатывается одинаково и никаких проблем нет.
Как я уже сказал, замена пары байт кода в товем тесте сравнивает результаты (правда вывод этот основан на относительных результатах, так как С++-ного проекта у меня нет).
В общем, присылай С++-шный проект посмотрим...
... << RSDN@Home 1.1.4 beta 3 rev. 273>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Serginio1, Вы писали:
S> А считывать в буффер byte[] , из которого через BlockCopy копировать в массив. Вопервых нетипизированный ArrayList это боксинг и унбоксинг,затраты памяти фрагментация памяти.
Семантика нарушается. У него потенциально можно грузить int-ы в массив произвольного теста. Хотя конечно для теста это пофигу, а использовать ArrayList здесь просто садизм.
Я бы извернул нечто вроде:
private T[] CreateArray(string fileName)
{
using (BinaryReader reader =
new BinaryReader(File.Open(fileName, FileMode.Open)))
{
long len = reader.BaseStream.Length / 4;
T[] array = new T[len];
for (int i = 0; i < len; i++)
array[i] = CreateElement(reader.ReadInt32());
return array;
}
}
уж если нужно как ПК хочет.
... << RSDN@Home 1.1.4 beta 3 rev. 273>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.