2015-04-04 5 views
0

У меня есть два вопроса относительно операторов сравнения в C++:Операторы сравнения в C++

  1. Когда зацикливание, какой из двух способов является более эффективным (тип mdata и mfirst_free является double*, тоже):

for (double *p = mdata; p < mfirst_free; ++p) { .. }

for (double *p = mdata; p != mfirst_free; ++p) { .. }

Я догадываюсь, что тот же ответ верен для всех примитивных типов - int, double и указателей. Правильно?
Я знаю, что версия < более безопасна, потому что если я как-то начну с большего указателя, чем ожидалось, это не будет бесконечным циклом. Но, когда я уверен, что я не получу недопустимый ввод, какая версия более эффективна?

  1. При переопределении операторов сравнения в моих собственных классах существуют ли наиболее эффективные комбинации, и существует ли минимальная требуемая (или рекомендуемая) комбинация для вывода остальных?

Давайте рассмотрим гипотетическую ситуацию, пытаясь сэкономить каждый процессорный цикл - каковы наилучшие варианты? Есть ли другие, которые являются более элегантными?

+1

Я сравниваю указатели на двойниках, а не сам по себе. Это цикл по массиву двойников, не сохраняя границ как целые числа, но и как указатели. – tbukic

+0

Жаль, что я этого не заметил. Но все же, я не думаю, что вопрос о том, что эффективнее, бесполезен. Вы должны проверить сгенерированный код сборки для каждого из ваших вариантов. –

+1

Что касается второго вопроса: (1) Я думаю, вы имеете в виду «эффективный», а не «эффективный», и (2) во многом зависит от того, используете ли вы библиотечные средства, такие как стандартная библиотека C++, которые основаны на неясных причинах используя 'operator <' вместо 'compare'. –

ответ

5

Как всегда, посмотрите сами и посмотрите на сгенерированный код. С г ++ 4.8 на Ubuntu 14.04, вы получите

movq -16(%rbp), %rax 
    movq %rax, -32(%rbp) 
    jmp .L2 
.L3: 
    addq $8, -32(%rbp) 
.L2: 
    movq -32(%rbp), %rax 
    cmpq -8(%rbp), %rax 
    jb .L3 

movq -16(%rbp), %rax 
    movq %rax, -24(%rbp) 
    jmp .L4 
.L5: 
    addq $8, -24(%rbp) 
.L4: 
    movq -24(%rbp), %rax 
    cmpq -8(%rbp), %rax 
    jne .L5 

Как вы можете видеть, единственным значимым отличием является jb .L3 против jne .L5.

Итак, я бы сказал, что оба они эквивалентны с точки зрения производительности.


Отвечая на второй вопрос, минимум, необходимый для сравнения operator<. Вы можете вывести все остальное из этого, например.

bool operator==(const T &x, const T &y) { 
    return !(x < y) && !(y < x); 
} 

bool operator!=(const T &x, const T &y) { 
    return !(x == y); 
} 

bool operator>(const T &x, const T &y) { 
    return y < x; 
} 

bool operator<=(const T &x, const T &y) { 
    return !(y < x); 
} 

bool operator>=(const T &x, const T &y) { 
    return !(x < y); 
} 

Конечно, это верно только в том случае, если у вас есть обычная семантика сравнения.

1

Насколько я знаю, это действительно не имеет значения. Я считаю, что

p != mfirst_free 

будет переведен на что-то в сборе похож на

loop   
       CMP p mfirst_free 
       BEQ exit 
       {...body...} 
       B  loop 
    exit 

в то время как

p < mfirst_free 

переведет к тому же, но вместо инструкции Beq было бы BGE. Такое же количество команд займет столько же времени, сколько нужно.

-Есть различные способы, чтобы перевести для цикла к сборке, но моя точка здесь является то, что каждый оператор сравнения, который вы используете, приведет к тому же времени работы, насколько я know.-

+1

Re "это действительно имеет значение", вы намеревались иметь "не" там? –

+1

Омг, мой плохой. Да, это должно быть «не». – Xaphanius

2

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

В реальном выражении вы спрашиваете, берет ли инструкция «ветвь, если она не равна», занимает меньше времени, чем инструкция «ветвь, если меньше». Во всех процессорах, с которыми я когда-либо сталкивался, ответ будет «нет» - если вы не ошибетесь - ошибаетесь в неверном прогнозе ветвления.

1

Если переменная счетчика является целым числом или указателем, то, вероятно, нет никакой разницы в эффективности. Эффект тот же, пока переменная просто увеличивается (на единицу) и начинает меньше или равно и конечное значение.

Но итераторы C++ не обязательно реализуют operator<. Фактически для этого требуется только RandomAccessIterator (см. http://en.cppreference.com/w/cpp/iterator). Поэтому C++ 11 переводит for(auto&& v : values) в for(auto it = std::begin(values); it != std::end(values); ++it). Например, std::forward_list имеет только ForwardIterators, поэтому operator< не может использоваться для его повторения.

Если вы используете OpenMP для распараллеливания operator< необходимо:

#pragma omp parallel for 
for(auto it = std::begin(values); it < std::end(values); ++it) 

Потому что нужно разделить петли на более мелкие петли.

Я думаю, что в целом было бы лучше использовать operator!= для совместимости со всеми типами итераторов, если вы не сделаете что-то другое, кроме итерации (приращение более чем на один и т. Д.), Используйте значения с плавающей запятой в качестве счетчика или используйте OpenMP.

+0

Я принял это из-за большого совета по использованию, но Олаф отредактировал ответ и только полностью ответил на мой вопрос. Во всяком случае, спасибо за подсказку! – tbukic

Смежные вопросы