Ядро Linux в комментариях

       

Sigaddset


sigaddset добавляет один сигнал в набор, т.е. устанавливает один разряд набора.

Преобразует нумерацию сигналов с основания1 в основание 0, чтобы достигнуть соответствия нумерации разрядов в поразрядных операциях.

Если сигналы помещаются в одно значение типа unsigned long, соответствующий разряд устанавливается в нем.

В противном случае sigaddset должна предпринять дополнительные действия, определив соответствующий элемент в массиве и установив корректный разряд в нем.

Строка , подобно и другому коду в этом файле, на первый взгляд вызывает легкое замешательство. Сущностью всего кода ядра является быстродействие. Таким образом, можно было бы рассчитывать, что вместо решения на этапе выполнения, подобного:

if (_NSIG_WORDS == 1) set->sig[0] |= 1UL << sig; else set->sig[sig / _NSIG_BPW] |= 1UL << (sig % _NSIG_BPW);

должно присутствовать решение на этапе компиляции:

#if (_NSIG_WORDS == 1) set->sig[0] |= 1UL << sig; #else set->sig[sig / _NSIG_BPW] |= 1UL << (sig % _NSIG_BPW); #endif

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

Туман рассеивается, стоит только подумать о том, что те же действия делает также и оптимизатор. Оптимизатор gcc интеллектуален настолько, что способен выяснить, что оператор if может работать только в одной своей части и удалить код, который никогда не будет выполняться. Результирующий объектный код оказывается одинаковым как для версии «этапа выполнения», так и для версии «этапа компиляции».

Но почему бы не счесть второй вариант кода, основанный на использовании препроцессора, в любом случае более удачным, вдобавок предположив наличие не очень хорошего оптимизатора? Нет необходимости. С одной стороны, такой код более труден в понимании. Разница в читабельности становится более ощутимой, когда речь идет о более сложном коде. Возьмите, например, фрагмент с оператором switch в sigemptyset, который начинается в строке . Сейчас упомянутый фрагмент кода выглядит так:

switch (_NSIG_WORDS) { default: memset(set, 0, sizeof(sigset_t)); break; case 2: set->sig[1] = 0; case 1: set->sig[0] = 0; break; }


(Отметьте преднамеренную перестановку порядка случаев 2 и 1.) Если код переписать так, чтобы задействовать препроцессор, он может выглядеть так:

#if ((_NSIG_WORDS != 2) && \ (_NSIG_WORDS != 1)) memset(set, 0, sizeof(sigset_t)); #else /* (_NSIG_WORDS is 2 or 1. */ #if (_NSIG_WORDS == 2) set->sig[1] = 0; #endif set->sig[0] = 0; #endif /* _NSIG_WORDS test. */

Оптимизатор gcc сгенерирует один и тот же объектный код для обоих случаев. Какая из версий кода более удобочитаема?

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


Содержание раздела