У меня есть 2 задачи TaskA и TaskB, TaskA будет обработан 10 раз, TaskB будет обработан 20 раз. Теперь есть два решения, как показано ниже
Решение 1:
for (int i = 0; i < 10; ++i)
{
// do taskA
}
for (int i = 0; i < 20; ++i)
{
// do taskB
}
Решение 2:
for (int i = 0; i < 20; ++i)
{
if (i < 10)
{
//do taskA
}
// do taskB
}
Я хочу спросить, или решение 1 или решение 2 лучше для производительности и чистого кода?
Есть ли разница в производительности — вот что такое A и TaskB. Но ИМО, имея один цикл с условием (i>10
) сделает код менее читабельным.
Таким образом, вы можете сделать эквивалентный код более понятным способом:
for (int i = 0; i < 10; ++i)
{
// do taskA
// do taskB
}
for (int i = 10; i < 20; ++i)
{
// do taskB
}
Это эквивалентно решению 2, и насколько лучше зависит от задач A и B.
Как уже указывалось несколько раз, все зависит от того, что представляют собой «задачи».
Можно спорить о «стиле» или «удобочитаемости», но это явно субъективно.
Так что не так много осталось для задача ответ, кроме двух пунктов:
Что касается лучших практик, я хотел бы сослаться на Разделение проблем. Если задачи полностью независимы (а они, очевидно, есть — иначе у вас не будет даже возможности изменить порядок выполнения), тогда принцип Разделение проблем предлагает поставить исполнение в два отдельных цикла. Так что это способствует вашему первый решение. Можно даже подумать о том, чтобы пойти еще дальше, и разделить их на два метода:
void executeA(int times) {
for (int i = 0; i < times; ++i) {
// do taskA
}
}
void executeB(int times) {
for (int i = 0; i < times; ++i) {
// do taskB
}
}
и замените циклы этими методами:
executeA(10);
executeB(20);
(здесь можно пойти еще дальше, но может упустить смысл вопроса)
Относительно производительности, как правило, не будет большой разницы, хотя детали могут зависеть от задач и компилятора. Современные компиляторы оптимизируют довольно хорошо, и может быть даже развернуть циклы, так что первое решение будет эквивалентно
taskA();
... // 10 times
taskA();
taskB();
... // 20 times
taskB();
и второе решение будет эквивалентно
taskB();
taskA();
... // 10 times
taskB();
taskA();
taskB();
taskB();
... // 10 times
taskB();
taskB();
Даже если петли не развернуты, Прогнозирование отрасли позаботится о if
, Тем не менее первый Решение тогда будет по-прежнему предпочтительным по нескольким причинам:
Последнее относится к тому факту, что вы можете тривиально распараллелить простой цикл, как
for (int i = 0; i < times; ++i) {
taskA();
}
Например, в Java (или C ++, если у вас есть соответствующая инфраструктура), вы можете просто выбросить 10 экземпляров taskA
в пул потоков. Также аннотирую такой цикл чем-то вроде #pragma omp parallel
легко возможно (хотя я не знаком с OpenMP). Если есть if
при условии, что это будет мешать большинству подходов распараллеливания.
На основании этих наблюдений я бы проголосовал за решение 1.
Как уже упоминалось, многое зависит от того, какой тип задачи выполняется в этих циклах. Хотя по умолчанию следует выбрать вариант 1 для удобства чтения.
Хотя есть один случай, когда вариант 1 должно быть выбор по причинам больше, чем синтаксический сахар. Если задача A и задача B будут работать на своих собственных блоках памяти местность ссылки будет, по всей вероятности, опередит вариант 2.
например:
for(int i = 0 ; i < 10; i++){
doSomething = dataBlockForTaskA[i]; // lots of cache hits
}
против
for (int j = 0; j < 20; j++){
doSomething = dataBlockForTaskB[j]; // fetches some memory around BlockB
if ( j < 10 ){
doSOmething = dataBlockForTaskA[j] // oops cache miss
}
}
В соответствии с вашим примером решение 2 будет лучше, если учесть, что цикл выполняется только один раз. Но вам придется учитывать то, что логика в задаче A против задачи B.