Для проблемы оптимизации параметров на работе я написал генетический алгоритм, чтобы найти хорошие настройки, потому что решение грубой силы неосуществимо. К сожалению, когда я возвращаюсь утром, большую часть времени мне представляется StackOverflowException
.
Я использую F # довольно долгое время, поэтому я знаю о TCO и необходимости в функциях с аргументами аккумулятора и обычно использую эту форму.
После многих поисков я думаю, что смог прибить код, вызвавший исключение:
breedPopulation alive |> simulate (generation + 1) lastTime ewma
breedPopulation
генерирует новое поколение от индивидов в текущем alive
. Затем следующий раунд/поколение начинается с вызова simulate
. Когда я смотрю на разборку (total noob), я вижу несколько pop
и ret
, поэтому он не выглядит как обычный (не хвост) вызов.
mov rcx,qword ptr [rbp+10h]
mov rcx,qword ptr [rcx+8]
mov rdx,qword ptr [rbp-40h]
cmp dword ptr [rcx],ecx
call 00007FFA3E4905C0
mov qword ptr [rbp-0F0h],rax
mov r8,qword ptr [rbp-0F0h]
mov qword ptr [rbp-80h],r8
mov r8,qword ptr [rbp-78h]
mov qword ptr [rsp+20h],r8
mov r8d,dword ptr [rbp+18h]
inc r8d
mov rdx,qword ptr [rbp+10h]
mov r9,qword ptr [rbp-20h]
mov rcx,7FFA3E525960h
call 00007FFA3E4A5040
mov qword ptr [rbp-0F8h],rax
mov rcx,qword ptr [rbp-0F8h]
mov rdx,qword ptr [rbp-80h]
mov rax,qword ptr [rbp-0F8h]
mov rax,qword ptr [rax]
mov rax,qword ptr [rax+40h]
call qword ptr [rax+20h]
mov qword ptr [rbp-100h],rax
mov rax,qword ptr [rbp-100h]
lea rsp,[rbp-10h]
pop rsi
pop rdi
pop rbp
ret
После отбрасывания оператора трубы и постановки разведения в нормальное положение, разборки различны.
// simulate (generation + 1) lastTime ewma (breedPopulation alive)
mov ecx,dword ptr [rbp+18h]
inc ecx
mov dword ptr [rbp-30h],ecx
mov rcx,qword ptr [rbp-20h]
mov qword ptr [rbp-38h],rcx
mov rcx,qword ptr [rbp-80h]
mov qword ptr [rbp-0F0h],rcx
mov rcx,qword ptr [rbp+10h]
mov rcx,qword ptr [rcx+8]
mov rdx,qword ptr [rbp-48h]
cmp dword ptr [rcx],ecx
call 00007FFA3E4605C0
mov qword ptr [rbp-0F8h],rax
mov rax,qword ptr [rbp-0F8h]
mov qword ptr [rbp+30h],rax
mov rax,qword ptr [rbp-0F0h]
mov qword ptr [rbp+28h],rax
mov rax,qword ptr [rbp-38h]
mov qword ptr [rbp+20h],rax
mov eax,dword ptr [rbp-30h]
mov dword ptr [rbp+18h],eax
nop
jmp 00007FFA3E47585B
Это определенно короче и с окончательным jmp
еще лучше, чем хвост.
Поэтому я хочу понять, если и почему проблема |>
кажется проблемой, и когда это имеет значение - ведь это первый раз, когда я укусил меня через годы. При каких обстоятельствах это происходит и что мы должны соблюдать?
Обновление: После Guy указано, что мои листинги не являются IL, а сборкой, я сначала переформулировал вопрос. Вот что я узнал с помощью ILSpy:
С оператором | >
Посмотрев на декомпилированный С#, код, кажется, прыгает назад и вперед между
internal static FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]> [email protected](Universe x, System.Threading.ManualResetEvent pleaseStop, int generation, System.DateTime lastTime, FSharpOption<double> ewma)
{
return new [email protected](x, pleaseStop, generation, lastTime, ewma);
}
и
// internal class [email protected]
public override System.Tuple<System.Tuple<float, float>, LbpArea[]>[] Invoke(Types.Genome[] population)
{
LbpArea[][] array = ArrayModule.Parallel.Map<Types.Genome, LbpArea[]>(this.x.genomeToArray, population);
FSharpFunc<System.Tuple<System.Tuple<float, float>, LbpArea[]>, float> accessFitness = this.x.accessFitness;
System.Tuple<System.Tuple<float, float>, LbpArea[]>[] array2 = ArrayModule.Filter<System.Tuple<System.Tuple<float, float>, LbpArea[]>>(new [email protected](accessFitness), ArrayModule.Parallel.Map<LbpArea[], System.Tuple<System.Tuple<float, float>, LbpArea[]>>(new [email protected](this.x), array));
if (array2 == null)
{
throw new System.ArgumentNullException("array");
}
System.Tuple<System.Tuple<float, float>, LbpArea[]>[] array3 = ArrayModule.SortWith<System.Tuple<System.Tuple<float, float>, LbpArea[]>>(new [email protected](), array2);
this.x.Population = array3;
System.Tuple<System.DateTime, FSharpOption<double>> tuple = this.x.printProgress<float, LbpArea[]>(this.lastTime, this.ewma, this.generation, array3);
System.DateTime item = tuple.Item1;
FSharpOption<double> item2 = tuple.Item2;
if (this.pleaseStop.WaitOne(0))
{
return array3;
}
Types.Genome[] func = this.x.breedPopulation(array3);
return [email protected](this.x, this.pleaseStop, this.generation + 1, item, item2).Invoke(func);
}
В IL-вызове вызова new
не существует tail.
op. С другой стороны, IL последних строк Invoke
читает
IL_00d3: call class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class BioID.GeneticLbp.Types/Genome[], class [mscorlib]System.Tuple`2<class [mscorlib]System.Tuple`2<float32, float32>, valuetype [BioID.Operations.Biometrics]BioID.Operations.Biometrics.LbpArea[]>[]> '<StartupCode$BioID-GeneticLbp>.$Universe'::'[email protected]'(class BioID.GeneticLbp.Universe, class [mscorlib]System.Threading.ManualResetEvent, int32, valuetype [mscorlib]System.DateTime, class [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1<float64>)
IL_00d8: ldloc.s 7
IL_00da: tail.
IL_00dc: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class BioID.GeneticLbp.Types/Genome[], class [mscorlib]System.Tuple`2<class [mscorlib]System.Tuple`2<float32, float32>, valuetype [BioID.Operations.Biometrics]BioID.Operations.Biometrics.LbpArea[]>[]>::Invoke(!0)
IL_00e1: ret
Я не знаю, что с этим делать.
Без | > оператора
Другая версия действительно отличается. Начиная с
internal static System.Tuple<System.Tuple<float, float>, LbpArea[]>[] [email protected](Universe x, System.Threading.ManualResetEvent pleaseStop, Unit unitVar0)
{
FSharpFunc<int, FSharpFunc<System.DateTime, FSharpFunc<FSharpOption<double>, FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]>>>> fSharpFunc = new [email protected](x, pleaseStop);
(([email protected])fSharpFunc).x = x;
(([email protected])fSharpFunc).pleaseStop = pleaseStop;
System.Tuple<System.Tuple<float, float>, LbpArea[]>[] population = x.Population;
Types.Genome[] func;
if (population != null && population.Length == 0)
{
func = x.lengthRandomlyIncreasing([email protected]@);
return FSharpFunc<int, System.DateTime>.InvokeFast<FSharpOption<double>, FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]>>(fSharpFunc, 0, System.DateTime.Now, null).Invoke(func);
}
FSharpFunc<LbpArea[], Types.Genome> arrayToGenome = x.arrayToGenome;
func = ArrayModule.Parallel.Map<System.Tuple<System.Tuple<float, float>, LbpArea[]>, Types.Genome>(new [email protected](arrayToGenome), population);
return FSharpFunc<int, System.DateTime>.InvokeFast<FSharpOption<double>, FSharpFunc<Types.Genome[], System.Tuple<System.Tuple<float, float>, LbpArea[]>[]>>(fSharpFunc, 0, System.DateTime.Now, null).Invoke(func);
}
он переходит в
// internal class [email protected]
public override System.Tuple<System.Tuple<float, float>, LbpArea[]>[] Invoke(int generation, System.DateTime lastTime, FSharpOption<double> ewma, Types.Genome[] population)
{
return [email protected](this.x, this.pleaseStop, generation, lastTime, ewma, population);
}
и, наконец,
internal static System.Tuple<System.Tuple<float, float>, LbpArea[]>[] [email protected](Universe x, System.Threading.ManualResetEvent pleaseStop, int generation, System.DateTime lastTime, FSharpOption<double> ewma, Types.Genome[] population)
{
while (true)
{
// Playing evolution...
if (pleaseStop.WaitOne(0))
{
return array3;
}
// Setting up parameters for next loop...
}
throw new System.ArgumentNullException("array");
}
TL;DR
Таким образом, использование оператора трубы резко изменило поток программы. Мое предположение заключается в том, что между этими двумя функциями происходит то, что в конечном итоге вызывает исключение.
Я уже читал Tail Calls в F #, но я не думаю, что это относится к этой ситуации, поскольку я не использую функцию возврата первого класса как значение (в моем коде F #).
Итак, остается вопрос: почему оператор трубы обладает этим разрушительным эффектом? Как я мог заранее знать/что мне нужно, чтобы следить за?
Обновление 2:
Вы можете найти приведенную версию примера на GitHub. Убедитесь, что оператор inline
|>
изменяет результат IL, чего я не ожидал.
При уменьшении примера, с небольшой удачей, я смог найти реальный источник исключения. Вы можете проверить ветку на гораздо более минимальный вариант. В конце концов, это не имеет ничего общего с трубой, но я все равно не получаю его, потому что там есть хвостовая рекурсия.
Но мои первоначальные вопросы остаются. Я просто добавляю еще один.:)