У меня есть метод, в котором производительность действительно важна (я знаю, что преждевременная оптимизация - это корень всего зла. Я знаю, что должен, и я сделал профиль моего кода. В этом приложении каждая десятая секунды, которую я сохраняю, является большой победой.) Этот метод использует различные эвристики для генерации и возврата элементов. Эвристика используется последовательно: первая эвристика используется до тех пор, пока она больше не сможет возвращать элементы, затем используется вторая эвристика, пока она больше не сможет возвращать элементы и т. Д. До тех пор, пока не будут использованы все эвристики. При каждом вызове метода я использую переключатель для перехода к правильной эвристике. Это уродливо, но хорошо работает. Вот некоторые псевдо-кодКак я могу реорганизовать этот код с учетом производительности?
class MyClass
{
private:
unsigned int m_step;
public:
MyClass() : m_step(0) {};
Elem GetElem()
{
// This switch statement will be optimized as a jump table by the compiler.
// Note that there is no break statments between the cases.
switch (m_step)
{
case 0:
if (UseHeuristic1())
{
m_step = 1; // Heuristic one is special it will never provide more than one element.
return theElem;
}
m_step = 1;
case 1:
DoSomeOneTimeInitialisationForHeuristic2();
m_step = 2;
case 2:
if (UseHeuristic2())
{
return theElem;
}
m_step = 3;
case 3:
if (UseHeuristic3())
{
return theElem;
}
m_step = 4; // But the method should not be called again
}
return someErrorCode;
};
}
Как я уже сказал, это работает, и это эффективно, так как при каждом вызове, выполнение прыжков прямо там, где оно должно быть. Если эвристика не может обеспечить элемент, m_step увеличивается (так что в следующий раз мы не будем повторять эту эвристику снова), и потому что нет инструкции break, следующая эвристика проверяется. Также обратите внимание, что некоторые шаги (например, шаг 1) никогда не возвращают элемент, но являются однократной инициализацией для следующей эвристики.
Инициативы причины не все сделано заранее, так как они никогда не понадобятся. Всегда возможно (и часто) для GetElem не получать вызов снова после того, как он возвратил элемент, даже если есть все еще элементы, которые он мог бы вернуть.
Хотя это эффективная реализация, я нахожу ее действительно уродливой. Речь идет о взломе; использование его без перерыва также хакерское; метод становится очень длинным, даже если каждая эвристика инкапсулирована в свой собственный метод.
Как я должен реорганизовать этот код, чтобы он был более читабельным и элегантным, сохраняя его максимально эффективным?
Рефакторинг как правило, не собирается сделать код более эффективным - просто более удобным для чтения и ремонтопригодны. Эти два не должны делаться одновременно. Оптимизация часто делает код менее читабельным и поддерживаемым. –
Чувствуется, что вы внедрили «возврат урожая» вручную. Довольно круто! –
@Larry Watanabe: Вы правы, оптимизация и читаемость часто являются конкурирующими целями. Но этот код казался мне настолько хакерским, что мне было интересно, есть ли способ улучшить его. –