Параметр, который вы, вероятно, ищете, имеет интуитивно понятное имя base
.
base (число или строка категориальных координат)
Устанавливает, где рисуется основание бара (в единицах измерения позиции). В «стековом» или «относительном» бармоде трассы, которые устанавливают «базовый», будут исключены и выведены в режиме «наложения».
blockquote>Вы можете использовать здесь список или просто одно значение.
По крайней мере, бары теперь смещены. Затем нам нужно установить
zeroline
изyaxis
вFalse
, чтобы скрыть его и, наконец, добавить наш собственный зеролин черезshapes
.import plotly plotly.offline.init_notebook_mode() val_celcius = [-50, 0, 50, 100] val_fahrenheit = [c * 1.8 for c in val_celcius] # we don't need +32 here because of the shift by `base` x = [i for i, _ in enumerate(val_celcius)] data = plotly.graph_objs.Bar(x=[0, 1, 2, 3], y=val_fahrenheit, text=['{}°C'.format(c) for c in val_celcius], base=32) layout = plotly.graph_objs.Layout(yaxis={'zeroline': False}, shapes=[{'type': 'line', 'x0': 0, 'x1': 1, 'xref': 'paper', 'y0': 32, 'y1': 32, 'yref': 'y'}]) fig = plotly.graph_objs.Figure(data=[data], layout=layout) plotly.offline.iplot(fig)
Давайте использовать пример кода от другого вопроса . Скомпилируйте его, но скажите gcc не собираться:
gcc -std=c99 -S -O2 test.c
Теперь позволяют нам посмотреть эти _atoi
функция в результанте файл test.s (gcc 4.0.1 на Mac OS 10.5):
.text
.align 4,0x90
_atoi:
pushl %ebp
testl %eax, %eax
movl %esp, %ebp
movl %eax, %ecx
je L3
.align 4,0x90
L5:
movzbl (%ecx), %eax
testb %al, %al
je L3
leal (%edx,%edx,4), %edx
movsbl %al,%eax
incl %ecx
leal -48(%eax,%edx,2), %edx
jne L5
.align 4,0x90
L3:
leave
movl %edx, %eax
ret
компилятор выполнил оптимизацию последнего вызова на этой функции. Мы можем сказать, потому что нет никакого call
инструкция в том коде, тогда как исходный C код ясно имел вызов функции. Кроме того, мы видим jne L5
инструкция, которая переходит назад в функции, указывая на цикл, когда не было ясно никакого цикла в коде C. Если Вы перекомпилируете с выключенной оптимизацией, то Вы будете видеть строку, которая говорит call _atoi
, и Вы также не будете видеть обратных переходов.
, Можно ли автоматизировать, это - другой вопрос. Специфические особенности ассемблерного кода будут зависеть от кода, который Вы компилируете.
Вы могли обнаружить его программно, я думаю. Заставьте функцию распечатать текущее значение указателя вершины стека (зарегистрируйте ESP на x86). Если функция печатает то же значение для первого вызова, как это делает для рекурсивного вызова, то компилятор выполнил оптимизацию последнего вызова. Эта идея требует изменения функции, которую Вы надеетесь наблюдать, тем не менее, и это могло бы влиять, как компилятор принимает решение оптимизировать функцию. Если тест успешно выполняется (печатает то же значение ESP оба раза), то я думаю, что разумно предположить, что оптимизация была бы также выполнена без Вашего инструментария, но если тест перестанет работать, то мы не будем знать, был ли отказ из-за добавления кода инструментария.
РЕДАКТИРОВАНИЕ Мое исходное сообщение также предотвратило GCC от фактического выполнения последнего вызова eliminations. Я добавил, что некоторая дополнительная ловкость ниже этого дурачит GCC в выполнение устранения последнего вызова так или иначе.
Подробно останавливающийся на ответе Steven, можно программно проверить, чтобы видеть, есть ли у Вас тот же стековый фрейм:
#include <stdio.h>
// We need to get a reference to the stack without spooking GCC into turning
// off tail-call elimination
int oracle2(void) {
char oracle; int oracle2 = (int)&oracle; return oracle2;
}
void myCoolFunction(params, ..., int tailRecursionCheck) {
int oracle = oracle2();
if( tailRecursionCheck && tailRecursionCheck != oracle ) {
printf("GCC did not optimize this call.\n");
}
// ... more code ...
// The return is significant... GCC won't eliminate the call otherwise
return myCoolFunction( ..., oracle);
}
int main(int argc, char *argv[]) {
myCoolFunction(..., 0);
return 0;
}
При вызывании функции нерекурсивно, передайте в 0 параметр проверки. Иначе передача в оракуле. Если рекурсивный вызов хвоста, который должен был быть устранен, не был, то Вам сообщат во времени выполнения.
При проверении этого, похоже, что моя версия GCC не оптимизирует первый последний вызов, но остающиеся последние вызовы оптимизированы. Интересный.
Посмотрите на сгенерированный ассемблерный код и посмотрите, использует ли он call
или jmp
инструкция для рекурсивного вызова на x86 (для другой архитектуры, ищите соответствующие инструкции). Можно использовать nm
и objdump
для получения просто блока, соответствующего функции. Рассмотрите следующую функцию:
int fact(int n)
{
return n <= 1 ? 1 : n * fact(n-1);
}
Компиляция как [1 113]
gcc fact.c -c -o fact.o -O2
Затем чтобы протестировать, если это использует хвостовую рекурсию:
# get starting address and size of function fact from nm
ADDR=$(nm --print-size --radix=d fact.o | grep ' fact , Когда работал на вышеупомянутой функции, этот факт "печати сценария является рекурсивным хвостом". Когда вместо этого скомпилировано с -O3
вместо -O2
, это любопытно печатает "факт, не рекурсивный хвост".
Примечание, что это могло бы привести к ложным отрицательным сторонам, как ehemient указанный в его комментарии. Этот сценарий только приведет к правильному ответу, если функция не будет содержать рекурсивных вызовов себя вообще, и это также не обнаруживает одноуровневую рекурсию (например, где A()
вызовы B()
, который звонит A()
). Я не могу думать о более устойчивом методе в данный момент, который не включает наличие человека, смотрят на сгенерированный блок, но по крайней мере можно использовать этот сценарий для легкого захвата блока, соответствующего конкретной функции в объектном файле.
| cut -d ' ' -f 1,2)
# strip leading 0's to avoid being interpreted by objdump as octal addresses
STARTADDR=$(echo $ADDR | cut -d ' ' -f 1 | sed 's/^0*\(.\)/\1/')
SIZE=$(echo $ADDR | cut -d ' ' -f 2 | sed 's/^0*//')
STOPADDR=$(( $STARTADDR + $SIZE ))
# now disassemble the function and look for an instruction of the form
# call addr <fact+offset>
if objdump --disassemble fact.o --start-address=$STARTADDR --stop-address=$STOPADDR | \
grep -qE 'call +[0-9a-f]+ <fact\+'
then
echo "fact is NOT tail recursive"
else
echo "fact is tail recursive"
fi
, Когда работал на вышеупомянутой функции, этот факт "печати сценария является рекурсивным хвостом". Когда вместо этого скомпилировано с -O3
вместо -O2
, это любопытно печатает "факт, не рекурсивный хвост".
Примечание, что это могло бы привести к ложным отрицательным сторонам, как ehemient указанный в его комментарии. Этот сценарий только приведет к правильному ответу, если функция не будет содержать рекурсивных вызовов себя вообще, и это также не обнаруживает одноуровневую рекурсию (например, где A()
вызовы B()
, который звонит A()
). Я не могу думать о более устойчивом методе в данный момент, который не включает наличие человека, смотрят на сгенерированный блок, но по крайней мере можно использовать этот сценарий для легкого захвата блока, соответствующего конкретной функции в объектном файле.
я слишком ленив для рассмотрения дизассемблирования. Попробуйте это:
void so(long l)
{
++l;
so(l);
}
int main(int argc, char ** argv)
{
so(0);
return 0;
}
компиляция и запущенный эта программа. Если это работает навсегда, хвостовая рекурсия была оптимизирована далеко. если уносит стек, это не было.
РЕДАКТИРОВАНИЕ: извините, читайте слишком быстро, OP хочет знать, оптимизировали ли его конкретной функции ее хвостовую рекурсию далеко. Хорошо...
... принцип является все еще тем же - если хвостовая рекурсия будет оптимизирована далеко, то стековый фрейм останется тем же. Необходимо смочь использовать функция следа , чтобы получить стековые фреймы из функции и определить, растут ли они или нет. Если хвостовая рекурсия будет оптимизирована далеко, то Вы будете иметь только один указатель возврата в буфере .
Простой метод: Создайте простую программу хвостовой рекурсии, скомпилируйте ее и скройте ее, чтобы видеть, оптимизирована ли она.
Просто понял, что у Вас уже было это в Вашем вопросе. Если Вы знаете, как считать блок, довольно легко сказать. Рекурсивные функции назовут себя (с "вызовом, маркируют") from within the function body, and a loop will be just "jmp маркировкой".
Подробно останавливаясь на ответе PolyThinker, вот конкретный пример.
int foo(int a, int b) {
if (a && b)
return foo(a - 1, b - 1);
return a + b;
}
i686-pc-linux-gnu-gcc-4.3.2 -Os -fno-optimize-sibling-calls
вывод:
00000000 <foo>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 8b 55 08 mov 0x8(%ebp),%edx 6: 8b 45 0c mov 0xc(%ebp),%eax 9: 85 d2 test %edx,%edx b: 74 16 je 23 <foo+0x23> d: 85 c0 test %eax,%eax f: 74 12 je 23 <foo+0x23> 11: 51 push %ecx 12: 48 dec %eax 13: 51 push %ecx 14: 50 push %eax 15: 8d 42 ff lea -0x1(%edx),%eax 18: 50 push %eax 19: e8 fc ff ff ff call 1a <foo+0x1a> 1e: 83 c4 10 add $0x10,%esp 21: eb 02 jmp 25 <foo+0x25> 23: 01 d0 add %edx,%eax 25: c9 leave 26: c3 ret
i686-pc-linux-gnu-gcc-4.3.2 -Os
вывод:
00000000 <foo>: 0: 55 push %ebp 1: 89 e5 mov %esp,%ebp 3: 8b 55 08 mov 0x8(%ebp),%edx 6: 8b 45 0c mov 0xc(%ebp),%eax 9: 85 d2 test %edx,%edx b: 74 08 je 15 <foo+0x15> d: 85 c0 test %eax,%eax f: 74 04 je 15 <foo+0x15> 11: 48 dec %eax 12: 4a dec %edx 13: eb f4 jmp 9 <foo+0x9> 15: 5d pop %ebp 16: 01 d0 add %edx,%eax 18: c3 ret
В первом случае, <foo+0x11>-<foo+0x1d>
требует у аргументов в пользу вызова функции, в то время как во втором случае, <foo+0x11>-<foo+0x14>
изменяет переменные и jmp
s к той же функции, где-нибудь после преамбулы. Это - то, что Вы хотите искать.
Я не думаю, что можно сделать это программно; существует слишком много возможного изменения. "Суть" функции может быть ближе к или еще дальше от запуска, и Вы не можете отличить это jmp
от цикла или условного выражения, не смотря на него. Это мог бы быть условный переход вместо a jmp
. gcc
мог бы оставить a call
в для некоторых случаев, но применяют одноуровневую оптимизацию вызова к другим случаям.
К вашему сведению "одноуровневые вызовы gcc" являются немного более общими, чем рекурсивные вызовы хвоста - эффективно, любой вызов функции, где многократное использование того же стекового фрейма хорошо, является потенциально одноуровневым вызовом.
[править]
Как пример, просто ища саморекурсивное call
введет в заблуждение Вас,
int bar(int n) {
if (n == 0)
return bar(bar(1));
if (n % 2)
return n;
return bar(n / 2);
}
GCC применит одноуровневую оптимизацию вызова к два из трех bar
вызовы. Я все еще назвал бы оптимизированным последним вызовом, с тех пор который единственный неоптимизированный вызов никогда не идет далее, чем единственный уровень, даже при том, что Вы найдете a call <bar+..>
в сгенерированном блоке.
Другой способ, которым я это проверил: