На сколько затратно выбрасывание исключения
От: Cynic Россия  
Дата: 28.02.15 15:23
Оценка:
Предположим я реализовал некоторый класс, у которого есть внутреннее поле значение которого не должно выходить за определённые пределы и мне нужно как то обработать во внешнем классе ситуацию когда это поле выходит за границы допустимого диапазона. Тут возможно два варианта. Либо я реализую в искомом классе исключение и обрабатываю его во внешнем, либо я создаю у класса поле возвращающее контролируемое значение и перед выполнением операции которая потенциально может вывести контролируемое значение за пределы допустимого диапазона, проверяю его.
Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?

p/s
Исходим из того, что при проверке значения выполняется простая операция, типа больше ли одно число другого, а не сравнение классов.
:)
Отредактировано 28.02.2015 15:24 Cynic . Предыдущая версия .
Re: На сколько затратно выбрасывание исключения
От: Qulac Россия  
Дата: 28.02.15 16:42
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Предположим я реализовал некоторый класс, у которого есть внутреннее поле значение которого не должно выходить за определённые пределы и мне нужно как то обработать во внешнем классе ситуацию когда это поле выходит за границы допустимого диапазона. Тут возможно два варианта. Либо я реализую в искомом классе исключение и обрабатываю его во внешнем, либо я создаю у класса поле возвращающее контролируемое значение и перед выполнением операции которая потенциально может вывести контролируемое значение за пределы допустимого диапазона, проверяю его.

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?

C>p/s

C>Исходим из того, что при проверке значения выполняется простая операция, типа больше ли одно число другого, а не сравнение классов.

Если использовать проектирование по контракту, должно быть и то и другое. Что бы это не было слишком накладным, есть смысл проверять вызовы только внешних систем к разрабатываемой. Подробнее тут: http://rsdn.ru/article/design/Code_Contracts.xml
Автор(ы): Тепляков Сергей Владимирович
Дата: 13.09.2010
Проектирование по контракту – это мощная техника разработки программного обеспечения (ПО), которая путем формализации взаимоотношений между компонентами позволяет создавать качественное, надежное и расширяемое ПО. В данной статье рассматриваются теоретические аспекты проектирования по контракту, изначально изложенные Бертраном Мейером, которые позволят понять всю ценность этой методики при разработке ПО.
Программа – это мысли спрессованные в код
Re: На сколько затратно выбрасывание исключения
От: Pavel Dvorkin Россия  
Дата: 28.02.15 18:24
Оценка: 2 (1) +1 -2
Здравствуйте, Cynic, Вы писали:

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?


Достаточно затратно. Исключение в Windows всегда идет через переключение в режим ядра.

When an exception occurs, whether it is explicitly raised by software or implicitly raised by hardware, a chain of events begins in the kernel. The CPU hardware transfers control to the kernel trap handler, which creates a trap frame (as it does when an interrupt occurs). The trap frame allows the system to resume where it left off if the exception is resolved. The trap handler also creates an exception record that contains the reason for the exception and other pertinent information.

...

If the exception occurred in user mode, the exception dispatcher does something more elaborate. As you'll see in Chapter 6, the Win32 subsystem has a debugger port and an exception port to receive notification of user-mode exceptions in Win32 processes. The kernel uses these in its default exception handling, as illustrated in Figure 3-6.

Debugger breakpoints are common sources of exceptions. Therefore, the first action the exception dispatcher takes is to see whether the process that incurred the exception has an associated debugger process. If so, it sends the first-chance debug message (via an LPC port) to the debugger port associated with the process that incurred the exception. (The message is sent to the session manager process, which then dispatches it to the appropriate debugger process.)

If the process has no debugger process attached, or if the debugger doesn't handle the exception, the exception dispatcher switches into user mode and calls a routine to find a frame-based exception handler. If none is found, or if none handles the exception, the exception dispatcher switches back into kernel mode and calls the debugger again to allow the user to do more debugging. (This is called the second-chance notification.)

All Win32 threads have an exception handler declared at the top of the stack that processes unhandled exceptions. This exception handler is declared in the internal Win32 start-of-process or start-of-thread function. The start-of-process function runs when the first thread in a process begins execution. It calls the main entry point in the image. The start-of-thread function runs when a user creates additional threads. It calls the user-supplied thread start routine specified in the CreateThread call.

(C) Соломон-Руссинович, Внутреннее устройство Windows
With best regards
Pavel Dvorkin
Отредактировано 28.02.2015 18:29 Pavel Dvorkin . Предыдущая версия .
Re[2]: На сколько затратно выбрасывание исключения
От: hardcase Пират http://nemerle.org
Дата: 28.02.15 20:27
Оценка: +2
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Достаточно затратно. Исключение в Windows всегда идет через переключение в режим ядра.


Что-то сомневаюсь я насчет управляемого кода. В нем все может быть иначе.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: На сколько затратно выбрасывание исключения
От: drol  
Дата: 28.02.15 22:25
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Достаточно затратно. Исключение в Windows всегда идет через переключение в режим ядра.


И причём здесь SEH, когда речь идёт об обычных исключениях внутри .NET ?
Re: На сколько затратно выбрасывание исключения
От: btn1  
Дата: 01.03.15 00:03
Оценка: +3 -1
Здравствуйте, Cynic, Вы писали:

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?


А попробовать самое очевидное — написать тестовый пример и сравнить — не?
Re: На сколько затратно выбрасывание исключения
От: Hacker_Delphi Россия  
Дата: 01.03.15 00:07
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Предположим я реализовал некоторый класс, у которого есть внутреннее поле значение которого не должно выходить за определённые пределы и мне нужно как то обработать во внешнем классе ситуацию когда это поле выходит за границы допустимого диапазона. Тут возможно два варианта. Либо я реализую в искомом классе исключение и обрабатываю его во внешнем, либо я создаю у класса поле возвращающее контролируемое значение и перед выполнением операции которая потенциально может вывести контролируемое значение за пределы допустимого диапазона, проверяю его.

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?

C>p/s

C>Исходим из того, что при проверке значения выполняется простая операция, типа больше ли одно число другого, а не сравнение классов.

  1. Использование исключений — затратно. не так, как SEH, но затратно.[/*]
  2. Тем не менее, кидать исключение — Design-by-default для .Net{/*]
Отсюда вывод: Все сильно зависит от использования библиотеки (класса). Если библиотека разработана для внутренних нужд — используй флажки или еще что-то. Если библиотека МОЖЕТ быть использована где-то в другом месте — лучше использовать исключения.
Как компромиссное решение — сделай так, как было в свое время сделано в Managed DirectX — есть флажок, кидаться ли исключениями — вот и все. Сделай такой флажок, по умолчанию выставь в true, а там, где сам работаешь с библиотекой — можешь отключать и проверять валидность значений в другом месте...
P.S. свое исключение лучше не пиши — используй стандартное ArgumentOutOfRangeException.
Если при компиляции и исполнении вашей программы не происходит ни одной ошибки — это ошибка компилятора :)))
Re: На сколько затратно выбрасывание исключения
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.03.15 02:22
Оценка: +1 -1
Здравствуйте, Cynic, Вы писали:

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?


Все относительно. Если исключений будут миллионы, то затратно. Там ведь и объект создается, и стек раскручивается.

Но в принципе исключение это довольно быстро. В релизе и без отладки можно выбрасывать миллион исключений в секунду.

А вот под отладкой первое исключение будет втыкать, так как в этом момент просыпается отладчик и что-то там думает.

Вообще, надо стараться не делать логику на исключениях. Любое правило не без исключений, но стараться надо. Если реализация без исключения выглядит сравнимо, то лучшее предпочесть ее. А исключения оставить для ошибок.

Хотя каюсь, сам иногда использую исключения для перехвата управления. Например, недавно писал автодополнение по ключевым словам в парсере. Самое просто оказалось сформировать списко и запихать его в исключение. Дальнейшая работа парсер в обычном режиме не имеет смысла, а протаскивать се через обычные возвращаемые значения просто неодъемно. Но сам понимаю, что хак.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: На сколько затратно выбрасывание исключения
От: andy1618 Россия  
Дата: 01.03.15 03:57
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?


Если возник такой вопрос — вероятно, что-то не так с дизайном программы.
К примеру, у нас в проекте логика такая: если генерируется исключение, значит что-то не так либо с входными данными,
либо с системой. В обоих случаях сообщаем пользователю о проблеме и ждем его действий.
Соответственно, тут уже не важно, займет отработка такой ситуации 10 тактов процессора или 10000.

А вот сам процесс проверок — да, должен быть очень быстрым.
Тут правило из Макконнелла — в публичных методах делаем полную проверку входных данных, в защищённых — используем ассёрты.
Re[3]: На сколько затратно выбрасывание исключения
От: Pavel Dvorkin Россия  
Дата: 01.03.15 04:13
Оценка: 14 (2) +2
Здравствуйте, hardcase, Вы писали:


H>Что-то сомневаюсь я насчет управляемого кода. В нем все может быть иначе.


Теоретически может, но для дотнета все же так.

Actually, .NET exceptions are implemented in terms of SEH exceptions, which means that they do go on a trip through kernel mode. See http://blogs.msdn.com/cbrumme/archive/2003/10/01/51524.aspx1 for Chris Brumme's explanation.

http://discourse.codinghorror.com/t/understanding-user-and-kernel-mode/405/20


Вот здесь call stack при throw в дотнете

http://geekswithblogs.net/akraus1/archive/2010/05/25/140064.aspx
With best regards
Pavel Dvorkin
Отредактировано 01.03.2015 5:07 Pavel Dvorkin . Предыдущая версия . Еще …
Отредактировано 01.03.2015 4:32 Pavel Dvorkin . Предыдущая версия .
Re[3]: На сколько затратно выбрасывание исключения
От: Pavel Dvorkin Россия  
Дата: 01.03.15 04:28
Оценка: +1 -1
Здравствуйте, drol, Вы писали:

D>И причём здесь SEH, когда речь идёт об обычных исключениях внутри .NET ?


"Обычные" исключения выбрасываются с помощью вызова Win API RaiseException.

Вот здесь рассмотрение того, что происходит в дотнете при throw из C#. Первый и второй CallStack дают исчерпывающую информацию. throw из дотнета приводит в конечном счете к вызову нативной RaiseException из KERNELBASE.dll, а она приводит к переключению в режим ядра и далее, как в моем первом сообщении в этом топике.

0028ed38 767db727 KERNELBASE!RaiseException+0x58, calling ntdll!RtlRaiseException
0028ed4c 68b9008c mscorwks!Binder::RawGetClass+0x20, calling mscorwks!Module::LookupTypeDef
0028ed5c 68b904ff mscorwks!Binder::IsClass+0x23, calling mscorwks!Binder::RawGetClass
0028ed68 68bfb96f mscorwks!Binder::IsException+0x14, calling mscorwks!Binder::IsClass
0028ed78 68bfb996 mscorwks!IsExceptionOfType+0x23, calling mscorwks!Binder::IsException
0028ed80 68bfbb1c mscorwks!RaiseTheExceptionInternalOnly+0x2a8, calling KERNEL32!RaiseExceptionStub
0028eda8 68ba0713 mscorwks!Module::ResolveStringRef+0xe0, calling mscorwks!BaseDomain::GetStringObjRefPtrFromUnicodeString
0028edc8 68b91e8d mscorwks!SetObjectReferenceUnchecked+0x19
0028ede0 68c8e910 mscorwks!JIT_Throw+0xfc, calling mscorwks!RaiseTheExceptionInternalOnly
0028ee44 68c8e734 mscorwks!JIT_StrCns+0x22, calling mscorwks!LazyMachStateCaptureState
0028ee54 68c8e865 mscorwks!JIT_Throw+0x1e, calling mscorwks!LazyMachStateCaptureState
0028eea4 02ffaecd (MethodDesc 0x7af08c +0x7d WindowsFormsApplication1.Form1.F1(System.Object, System.EventArgs)), calling mscorwks!JIT_Throw
0028eeec 02ffaf19 (MethodDesc 0x7af098 +0x29 WindowsFormsApplication1.Form1.F2()), calling 06370634
0028ef58 02ffae37 (MethodDesc 0x7a7bb0 +0x4f System.Windows.Forms.Control.OnClick(System.EventArgs))


http://geekswithblogs.net/akraus1/archive/2010/05/25/140064.aspx
With best regards
Pavel Dvorkin
Отредактировано 01.03.2015 5:18 Pavel Dvorkin . Предыдущая версия . Еще …
Отредактировано 01.03.2015 5:01 Pavel Dvorkin . Предыдущая версия .
Отредактировано 01.03.2015 5:00 Pavel Dvorkin . Предыдущая версия .
Re: На сколько затратно выбрасывание исключения
От: Sinix  
Дата: 01.03.15 09:36
Оценка: +1
Здравствуйте, Cynic, Вы писали:

C>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?


Простой, понятный, неправильный подход:

Запускаем, смотрим на результаты.
  Код
using System;
using System.Diagnostics;

public class Program
{
    static void Main()
    {
        const int Count = 100 * 1000;

        Measure("Fast, callstack  0", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeFast(0);
            }
            return Count;
        });
        Measure("Exc,  callstack  0", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeExc(0);
            }
            return Count;
        });
        Measure("Fast, callstack  1", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeFast(1);
            }
            return Count;
        });
        Measure("Exc,  callstack  1", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeExc(1);
            }
            return Count;
        }); 
        Measure("Fast, callstack 10", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeFast(10);
            }
            return Count;
        });
        Measure("Exc,  callstack 10", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeExc(10);
            }
            return Count;
        });
        Measure("Fast, callstack 20", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeFast(20);
            }
            return Count;
        });
        Measure("Exc,  callstack 20", () => 
        {
            for (int i = 0; i < Count; i++)
            {
                TryCallMeExc(20);
            }
            return Count;
        });
        Console.Write("Done.");
        Console.ReadKey();
    }
    static bool TryCallMeFast(int count)
    {
        return TryCallMeCore(count, false);
    }
    static bool TryCallMeExc(int count)
    {
        try
        {
            return TryCallMeCore(count, true);
        }
        catch
        {
            return false;
        }
    }

    const int CatchEvery = 5;
    static bool TryCallMeCore(int count, bool fail)
    {
        if (count < 0) throw new ArgumentOutOfRangeException("Dont be kiddy.");
        if (count == 0)
        {
            if (fail) throw new InvalidOperationException("As you wish.");

            return false;
        }

        if (count % CatchEvery == 1)
        {
            try
            {
                return TryCallMeCore(count - 1, fail);
            }
            catch
            {
                throw;
            }
        }
        else
        {
            return TryCallMeCore(count - 1, fail);
        }
    }

    static void Measure(string name, Func<long> callback)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var sw = Stopwatch.StartNew();
        var tmp = callback();
        sw.Stop();

        Console.WriteLine("{0,30}: {1,5}ms, ips: {2,16:F4} : {3,9}", name, sw.ElapsedMilliseconds, tmp / sw.Elapsed.TotalSeconds, tmp);
    }
}
Понимаем, что с "миллионом исключений в секунду" VladD2 явно погорячился



Правильный: читаем FDG. Лучше всю книгу, на крайний случай:
1. https://msdn.microsoft.com/en-us/library/ms229009(v=vs.110).aspx
Именно ваш случай. См tester-doer + try-parse pattern.

2. http://blogs.msdn.com/b/kcwalina/archive/2005/03/16/396787.aspx
Краткое изложение.

Do not use exceptions for normal flow of control. Except for system failures, there should generally be a way to write code that avoids exceptions being thrown.

Re[2]: На сколько затратно выбрасывание исключения
От: Cynic Россия  
Дата: 01.03.15 10:15
Оценка:
Здравствуйте, btn1, Вы писали:

B>Здравствуйте, Cynic, Вы писали:


C>>Вопрос в том, на сколько затратно выбрасывание исключение, по сравнению с проверкой значения поля перед выполнением операции?


B>А попробовать самое очевидное — написать тестовый пример и сравнить — не?


Как бы сравнить то я могу, а вот получить такого исчерпывающего ответа который дал Павел Дворкин выше, нет.
:)
Re[2]: На сколько затратно выбрасывание исключения
От: Cynic Россия  
Дата: 01.03.15 10:27
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:
...

Вы всё отлично пояснили, вот только хотелось бы более конкретную рекомендацию услышать.
Например возьмём такой пример: Есть у меня есть некий буфер, который может периодически переполняться и переполнение нужно как-то обрабатывать. Я могу решить эту проблему двумя путями:
При этом операции записи в буфер происходят довольно часто. Правильно ли я понимаю, что в данном случае лучше избегать исключений?
:)
Re[3]: На сколько затратно выбрасывание исключения
От: Sinix  
Дата: 01.03.15 10:50
Оценка: +1
Здравствуйте, Cynic, Вы писали:

C>При этом операции записи в буфер происходят довольно часто. Правильно ли я понимаю, что в данном случае лучше избегать исключений?

Неправильно При попытке записать мимо буфера в любом случае должно бросаться исключение. Иначе вы останетесь с испорченными данными и никак об этом не узнаете.

Если буфер — простой массив, то исключение бросят за вас, достаточно обернуть его в custom type exception (если требуется).

Если соблюдаются все три условия:
* попытки записать мимо буфера будут происходить постоянно;
* это нормальная ситуация;
* вызывающий код знает, как такие моменты обрабатывать.

(я что-то не могу себе такой сценарий представить, но пусть будет),
то: рядом с методом Write надо добавить TryWrite, который в случае облома не будет писать в буфер и просто вернёт false.

Прочитайте ссылки из моего ответа выше, там всё детально разжёвано.
Re[4]: На сколько затратно выбрасывание исключения
От: Cynic Россия  
Дата: 01.03.15 11:13
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, Cynic, Вы писали:


C>>При этом операции записи в буфер происходят довольно часто. Правильно ли я понимаю, что в данном случае лучше избегать исключений?

S>Неправильно При попытке записать мимо буфера в любом случае должно бросаться исключение. Иначе вы останетесь с испорченными данными и никак об этом не узнаете.

S>Если буфер — простой массив, то исключение бросят за вас, достаточно обернуть его в custom type exception (если требуется).


S>Если соблюдаются все три условия:

S>* попытки записать мимо буфера будут происходить постоянно;
S>* это нормальная ситуация;
S>* вызывающий код знает, как такие моменты обрабатывать.

S>(я что-то не могу себе такой сценарий представить, но пусть будет),

S>то: рядом с методом Write надо добавить TryWrite, который в случае облома не будет писать в буфер и просто вернёт false.

S>Прочитайте ссылки из моего ответа выше, там всё детально разжёвано.


Да речь только о производительности решения. Я могу обрабатывать исключение, а могу проверять доступное место. Вопрос в том, что более производительно.
:)
Re[5]: На сколько затратно выбрасывание исключения
От: Sinix  
Дата: 01.03.15 11:28
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Да речь только о производительности решения. Я могу обрабатывать исключение, а могу проверять доступное место. Вопрос в том, что более производительно.

И ещё раз см. мой первый ответ. Там и про производительность, и про "как оно правильно решается".
Re[3]: На сколько затратно выбрасывание исключения
От: Pavel Dvorkin Россия  
Дата: 01.03.15 13:55
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Например возьмём такой пример: Есть у меня есть некий буфер, который может периодически переполняться и переполнение нужно как-то обрабатывать. Я могу решить эту проблему двумя путями:

C> C>При этом операции записи в буфер происходят довольно часто. Правильно ли я понимаю, что в данном случае лучше избегать исключений?

Что означает "буфер может переполняться" ? Мы же в управляемом коде. Если это массив и выйти за его границы, то ArrayIndexOutOfBoundsException и так выбросится. Если же что-то иное — хотелось бы знать, что именно.
With best regards
Pavel Dvorkin
Re[4]: На сколько затратно выбрасывание исключения
От: Pavel Dvorkin Россия  
Дата: 01.03.15 16:43
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Если соблюдаются все три условия:

S>* попытки записать мимо буфера будут происходить постоянно;
S>* это нормальная ситуация;
S>* вызывающий код знает, как такие моменты обрабатывать.

S>(я что-то не могу себе такой сценарий представить, но пусть будет),


Такой сценарий можно себе представить, но не в дотнете. Впрочем, и в дотнете, наверное, можно, если включить unsafe код. Это "истинный" (то есть без реаллокаций) динамический массив.

Идея простая — резервируем под него на максимум. Скажем, на 512 Мб. А что — не жалко. Это же не выделение памяти, а только резервирование АП.
Коммитируем, скажем, 64 Кб в самом начале. Пока хватит.
Начинаем писать. Последовательно. Строки, например, одна за другой. Пока они в 64 Кб помещаются — нет проблем. Но мы не следим за тем, помещаются или нет, просто пишем.
Раньше или позже выйдем за пределы 64 Кб. Словим exception, обработаем его : коммитируем еще 64 Кб, повторим запись. И т.д.
Естественно, предполагаю, что записываемая строка длиной <64 Кб. Если не так — будет чуть сложнее, но принцип не изменится

Другой похожий пример — огромная сильно разреженная матрица. В этом случае резервируем на весь ее размер, и не коммитируем ничего. Сразу пишем, сразу exception, обрабатываем его — коммитируем 4 Кб (меньше нельзя, страница). При следующей записи либо попадем на уже выделенную страницу, либо опять exception и т.д.

Для первой задачи, впрочем, вполне можно (и ИМХО стоит) обойтись все же без исключений. Несложно же проверять при последовательной записи, хватит оставшегося места или нет, и если не хватит — добавить 64 Кб.

Для второй задачи тоже, конечно, можно без исключенйи обойтись, но придется список выделенных блоков иметь и по нему пробегать, что приведет O(1) к чему-то мало приличному.

Первую задачу я студентам даю в качестве упражнения
With best regards
Pavel Dvorkin
Re[4]: На сколько затратно выбрасывание исключения
От: drol  
Дата: 01.03.15 16:44
Оценка: 18 (2)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>throw из дотнета приводит в конечном счете к вызову нативной RaiseException из KERNELBASE.dll, а она приводит к переключению в режим ядра


Кто Вам это сказал ??? RaiseException совершенно не обязана переключаться в kernel mode. В случае 64-битного процесса RaiseException уходит в ядро только при подключенном отладчике, необработанном исключении и тому подобных ситуациях. Обычные же исключения обрабатываются полностью в user mode... Ну или мне счётчики kernel\user time врут...
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.