Я оцениваю переписывание части программного обеспечения реального времени с языка C/ассемблера на язык С++/ассемблера (по причинам, не относящимся к частям вопроса, абсолютно необходимо делать в сборке).
Прерывание происходит с частотой 3 & times; kHz, и для каждого прерывания около 200 различных вещей должны выполняться в последовательности. Процессор работает с частотой 300 МГц, что дает нам 100 000 циклов для выполнения этой работы. Это было решено в C с массивом указателей функций:
// Each function does a different thing, all take one parameter being a pointer
// to a struct, each struct also being different.
void (*todolist[200])(void *parameters);
// Array of pointers to structs containing each function parameters.
void *paramlist[200];
void realtime(void)
{
int i;
for (i = 0; i < 200; i++)
(*todolist[i])(paramlist[i]);
}
Скорость важна. Вышеуказанные 200 итераций выполняются 3000 раз в секунду, поэтому практически 600 000 итераций в секунду. Вышеописанное для цикла составляет пять циклов на итерацию, что дает общую стоимость 3 000 000 циклов в секунду, то есть 1% загрузки процессора. Оптимизация ассемблера может привести к четырем инструкциям, однако я опасаюсь, что мы сможем получить дополнительную задержку из-за доступа к памяти близко друг к другу и т.д. Короче говоря, я считаю, что эти пять циклов довольно оптимальны.
Теперь переписываем С++. Эти 200 вещей, которые мы делаем, связаны друг с другом. Существует подмножество параметров, которые все они нуждаются и используют, и имеют в своих соответствующих структурах. В реализации С++ их можно было бы таким образом наследовать от общего базового класса:
class Base
{
virtual void Execute();
int something_all_things_need;
}
class Derived1 : Base
{
void Execute() { /* Do something */ }
int own_parameter;
// Other own parameters
}
class Derived2 : Base { /* Etc. */ }
Base *todolist[200];
void realtime(void)
{
for (int i = 0; i < 200; i++)
todolist[i]->Execute(); // vtable look-up! 20+ cycles.
}
Моя проблема - поиск в vtable. Я не могу делать 600 000 запросов в секунду; на это будет приходиться более 4% загруженной загрузки ЦП. Кроме того, todolist никогда не меняется во время выполнения, он устанавливается только один раз при запуске, поэтому усилия по поиску функции, которую можно вызвать, действительно теряются. Задав себе вопрос "какой оптимальный конечный результат возможен", я смотрю на код ассемблера, заданный решением C, и переустанавливаю массив указателей на функции...
Каков чистый и правильный способ сделать это на С++? Создание хорошего базового класса, производных классов и т.д. Кажется совершенно бессмысленным, когда, в конце концов, снова выбираются указатели на параметры производительности.
Обновление (включая исправление, где начинается цикл):
Процессор - это ADSP-214xx, а компилятор - VisualDSP ++ 5.0. При включении #pragma optimize_for_speed
цикл цикла составляет 9 циклов. Сборка, оптимизирующая его в моем сознании, дает 4 цикла, однако я не тестировал его, чтобы он не гарантировался. Цикл С++ - 14 циклов. Я знаю, что компилятор мог бы сделать лучшую работу, однако я не хотел отклонять это как проблему с компилятором, потому что без полиморфизма все еще предпочтительнее во встроенном контексте, и выбор дизайна по-прежнему меня интересует. Для справки, здесь результирующая сборка:
С
i3=0xb27ba;
i5=0xb28e6;
r15=0xc8;
Здесь фактический цикл:
r4=dm(i5,m6);
i12=dm(i3,m6);
r2=i6;
i6=i7;
jump (m13,i12) (db);
dm(i7,m7)=r2;
dm(i7,m7)=0x1279de;
r15=r15-1;
if ne jump (pc, 0xfffffff2);
С++:
i5=0xb279a;
r15=0xc8;
Здесь фактический цикл:
i5=modify(i5,m6);
i4=dm(m7,i5);
r2=i4;
i4=dm(m6,i4);
r1=dm(0x3,i4);
r4=r2+r1;
i12=dm(0x5,i4);
r2=i6;
i6=i7;
jump (m13,i12) (db);
dm(i7,m7)=r2;
dm(i7,m7)=0x1279e2;
r15=r15-1;
if ne jump (pc, 0xffffffe7);
Между тем, я думаю, что нашел ответ. Наименьшее количество циклов достигается, делая как можно меньше. Я должен получить указатель данных, получить указатель на функцию и вызвать функцию с указателем данных в качестве параметра. При извлечении указателя индексный регистр автоматически изменяется константой, и можно точно так же, как и эта константа, равняться 1. Таким образом, вы снова обнаруживаете себя с массивом указателей на функции и массив указателей данных.
Естественно, предел - это то, что можно сделать в сборке, и это теперь изучено. Имея это в виду, я теперь понимаю, что, хотя естественно, что для того, чтобы ввести базовый класс, это было не совсем то, что соответствовало законопроекту. Поэтому я предполагаю, что ответ заключается в том, что если требуется массив указателей на функции, нужно сделать себе массив указателей на функции...