Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 635: combine boolean logic #5116

Merged
merged 11 commits into from
Sep 15, 2018
Merged

Fix 635: combine boolean logic #5116

merged 11 commits into from
Sep 15, 2018

Conversation

dsyme
Copy link
Contributor

@dsyme dsyme commented Jun 6, 2018

This fixes #635 to produce better code for boolean logic. It is based on a draft by @thinkbeforecoding. The effect is to remove extra branching in nested a && b || c kind of logic, e.g.

For a variation of the repro case

let simpleAndOrEq (c1: char) (c2:char)  = ((c1 = c2) && (c1 = c2)) || (c1 = c2)

The F# code is smaller and basically identical to the equivalent C# except with one branch switched

In earlier examples this provided a benefit for the JITted assembly code as well, at least in size, though I haven't double checked the particular example above.

To be conservative this PR ensures no code duplication occurs, it's a strict simplification. This means that other cases like isAlphaNumeric from the linked bug are also a bit smaller but not as small as the equivalent C#. This is because the F# TAST is not fully able to express the linearization of the decision logic in this sort of code without duplication sub-expressions somewhere along the way (or using "goto"):

let isAlphaNumEq (s:string) pos =
    let c = s.[pos]
    (c = '0' && c ='9') || (c ='A' && c ='Z') || (c ='a' && c = 'z')

C# fully linearizes this. I don't think we will be able to implement this except via further work at the IlxGen.fs stage, which is independent of this PR.

@dsyme dsyme changed the title Fix 635: combine boolean logic [CompilerPerf] Fix 635: combine boolean logic Jun 6, 2018
@TIHan
Copy link
Contributor

TIHan commented Jun 6, 2018

There are 4 test failures:

1) Error : FSharp-Tests-Core+TypecheckTests.type check neg17
System.Exception : neg17.err neg17.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg17.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg17.err]
line 2
 - neg17.fs(84,17,84,33): typecheck error FS0292: The type definitions for type 'PrivateUnionType' in the signature and implementation are not compatible because the accessibility specified in the signature is more than that specified in the implementation
 + neg17.fs(84,17,84,33): typecheck error FS0292: The Type definitions for type 'PrivateUnionType' in the signature and implementation are not compatible because the accessibility specified in the signature is more than that specified in the implementation
"; neg17.vserr neg17.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg17.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg17.vserr]
line 2
 - neg17.fs(84,17,84,33): typecheck error FS0292: The type definitions for type 'PrivateUnionType' in the signature and implementation are not compatible because the accessibility specified in the signature is more than that specified in the implementation
 + neg17.fs(84,17,84,33): typecheck error FS0292: The Type definitions for type 'PrivateUnionType' in the signature and implementation are not compatible because the accessibility specified in the signature is more than that specified in the implementation
"
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1645.Invoke(String message) in D:\j\w\release_ci_pa---866fd2c3\src\fsharp\FSharp.Core\printf.fs:line 1645

2) Error : FSharp-Tests-Core+TypecheckTests.type check neg43
System.Exception : neg43.err neg43.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg43.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg43.err]
line 2
 - neg43.fs(11,6,11,8): typecheck error FS0294: The type definitions for type 'DU' in the signature and implementation are not compatible because the implementation says this type may use nulls as a representation but the signature does not
 + neg43.fs(11,6,11,8): typecheck error FS0294: The Type definitions for type 'DU' in the signature and implementation are not compatible because the implementation says this type may use nulls as a representation but the signature does not
"; neg43.vserr neg43.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg43.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg43.vserr]
line 2
 - neg43.fs(11,6,11,8): typecheck error FS0294: The type definitions for type 'DU' in the signature and implementation are not compatible because the implementation says this type may use nulls as a representation but the signature does not
 + neg43.fs(11,6,11,8): typecheck error FS0294: The Type definitions for type 'DU' in the signature and implementation are not compatible because the implementation says this type may use nulls as a representation but the signature does not
"
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1645.Invoke(String message) in D:\j\w\release_ci_pa---866fd2c3\src\fsharp\FSharp.Core\printf.fs:line 1645

3) Error : FSharp-Tests-Core+TypecheckTests.type check neg57
System.Exception : neg57.err neg57.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg57.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg57.err]
line 2
 - neg57.fs(4,6,4,9): typecheck error FS0314: The type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
 + neg57.fs(4,6,4,9): typecheck error FS0314: The Type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
"; neg57.vserr neg57.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg57.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg57.vserr]
line 2
 - neg57.fs(4,6,4,9): typecheck error FS0314: The type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
 + neg57.fs(4,6,4,9): typecheck error FS0314: The Type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
"
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1645.Invoke(String message) in D:\j\w\release_ci_pa---866fd2c3\src\fsharp\FSharp.Core\printf.fs:line 1645

4) Error : FSharp-Tests-Core+TypecheckTests.type check neg58
System.Exception : neg58.err neg58.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg58.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg58.err]
line 2
 - neg58.fs(5,6,5,9): typecheck error FS0314: The type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
 + neg58.fs(5,6,5,9): typecheck error FS0314: The Type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
"; neg58.vserr neg58.bsl differ; "diff between [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg58.bsl] and [D:\j\w\release_ci_pa---866fd2c3\tests\fsharp\typecheck\sigs\neg58.vserr]
line 2
 - neg58.fs(5,6,5,9): typecheck error FS0314: The type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
 + neg58.fs(5,6,5,9): typecheck error FS0314: The Type definitions for type 'Foo' in the signature and implementation are not compatible because the field 'offset' was present in the implementation but not in the signature. Struct types must now reveal their fields in the signature for the type, though the fields may still be labelled 'private' or 'internal'.
"
   at Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThenFail@1645.Invoke(String message) in D:\j\w\release_ci_pa---866fd2c3\src\fsharp\FSharp.Core\printf.fs:line 1645

@thinkbeforecoding
Copy link
Contributor

Yes, the casing of the error message changed: 'the type of' vs 'the Type of'
It seems totally orthogonal to the actual change from this PR


override x.ToString() =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the change that seems to break tests.
the casing of 'type' has changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting this.

@thinkbeforecoding
Copy link
Contributor

I looked at the result, and the instruction layout is not exactly the same than C#, but there is no extra instruction anymore !

Great !
After benchmarking the change, there is a small improvement in perf which was expected.

@thinkbeforecoding
Copy link
Contributor

Using the following file:
http://www.ins.cwi.nl/projects/xmark/Assets/standard.gz
I make the following:

let txt = System.IO.File.ReadAllText "standard"
let mutable c = false
let len = txt.Length - 1
let sw = System.Diagnostics.Stopwatch.StartNew()
for _ in 1 .. 5 do
    for i in 0 .. len do
        c <- Test.isAlphaNum txt.[i]
sw.Stop()
printfn "%O" sw.Elapsed

And I get around 2.2 sec for previous version, and 2.7 sec for 'optimized' version, and 2.0 sec for cs version…
😢

@vasily-kirichenko
Copy link
Contributor

@thinkbeforecoding Could you use BenchmarkDotNet to benchmark you sample instead?

@dsyme
Copy link
Contributor Author

dsyme commented Jun 6, 2018

Using benchmark.net is sensible.

A bunch of very controlled profiling tests would be great. There are numerous variables we need to be careful about: processor, 32/64-bit, net core c netfx runtime, /optimize etc. Also branch prediction doesn't make it easy to generalise results.

@dsyme
Copy link
Contributor Author

dsyme commented Jun 6, 2018

@thinkbeforecoding could you post both benchmarks where you see improvements and regressions (and is the one above really a regression? I'll check too)

@thinkbeforecoding
Copy link
Contributor

I'm working on a better benchmark with benchmarkdotnet

@thinkbeforecoding
Copy link
Contributor

BenchmarkDotNet is giving:
Optimized FS: 771.4ms
Original FS: 608.7ms
Cs: 539.9ms

This is testing applying isAlphaNum on the given file...

@dsyme
Copy link
Contributor Author

dsyme commented Jun 6, 2018

@thinkbeforecoding thanks for testing. Please link a zip and give full details of OS, .NET runtime, architecture, runtime etc. thx

@dsyme
Copy link
Contributor Author

dsyme commented Jun 6, 2018

@thinkbeforecoding I couldn't unzip that archive - could you post it with ZIP please? thanks

@dsyme
Copy link
Contributor Author

dsyme commented Jun 6, 2018

@thinkbeforecoding On a quick test I got this on Windows 10 .NET 4.7.2 Intel(R) Xeon(R) CPU E5-1620 0 @ 3.60GHz, 3601 Mhz, 4 Core(s), 8 Logical Processor(s)

x64:

F# VS 15.7.2: 4.0
F# THIS PR: 3.7

x32:

F# VS 15.7.2: 5.1
F# THIS PR: 4.3

Those are just approximate but the numbers are pretty consistent. That's for this code:

let isAlphaNum (s:string) pos =
    let c = s.[pos]
    (c >= '0' && c<='9') || (c>='A' && c<='Z') || (c>='a' && c <= 'z')
    
[<EntryPoint>]
let main argv = 

    let txt = System.IO.File.ReadAllText @"c:\GitHub\dsyme\NInterpret\Platforms\NInterpret.Xamarin.Droid\obj\Debug\lp\16\jl\bin\R.txt"
    let mutable c = false
    let len = txt.Length - 1
    let sw = System.Diagnostics.Stopwatch.StartNew()
    for _ in 1 .. 10000 do
        for i in 0 .. len do
            c <- isAlphaNum txt i
    sw.Stop()
    printfn "%O" sw.Elapsed

    0

Compiling and running with

debug\net40\bin\fsc /platform:x64 a.fs && a.exe
debug\net40\bin\fsc /platform:x86 a.fs && a.exe
fsc /platform:x64 a.fs && a.exe
fsc /platform:x86 a.fs && a.exe

So that looks like speed up to me, but we should continue to test

@thinkbeforecoding
Copy link
Contributor

thinkbeforecoding commented Jun 7, 2018 via email

@thinkbeforecoding
Copy link
Contributor

thinkbeforecoding commented Jun 7, 2018

The .net core test on the same sample is giving similar results:

Intel Core i7-4650U CPU 1.70GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=2.1.300
  [Host]     : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT DEBUG
  DefaultJob : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT
  • Optimized F#: 646.9ms
  • C#: 543.7ms
  • Original F#: 599.1ms

@thinkbeforecoding
Copy link
Contributor

thinkbeforecoding commented Jun 7, 2018

More benchmark:

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
Intel Core i7-4650U CPU 1.70GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=2.1.300
  [Host] : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT DEBUG
  Clr    : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3110.0
  Core   : .NET Core 2.1.0 (CoreCLR 4.6.26515.07, CoreFX 4.6.26515.06), 64bit RyuJIT

Method Job Runtime Categories Mean Error StdDev Scaled ScaledSD
Optimized_AlphaNum Clr Clr AlphaNum 97.15 ms 1.6824 ms 1.2165 ms 0.93 0.01
Cs_AlphaNum Clr Clr AlphaNum 104.29 ms 0.9284 ms 0.6713 ms 1.00 0.00
Original_AlphaNum Clr Clr AlphaNum 98.98 ms 1.9299 ms 2.1451 ms 0.95 0.02
Optimized_NumAlpha Clr Clr NumAlpha 121.61 ms 1.7726 ms 1.5713 ms 1.16 0.03
Cs_NumAlpha Clr Clr NumAlpha 104.48 ms 2.3507 ms 2.0839 ms 1.00 0.00
Original_NumAlpha Clr Clr NumAlpha 139.68 ms 3.9307 ms 9.3416 ms 1.34 0.09
Optimized_AlphaNum Core Core AlphaNum 119.88 ms 2.2961 ms 2.0355 ms 0.92 0.02
Cs_AlphaNum Core Core AlphaNum 130.87 ms 0.3338 ms 0.2959 ms 1.00 0.00
Original_AlphaNum Core Core AlphaNum 119.79 ms 0.5994 ms 0.5313 ms 0.92 0.00
Optimized_NumAlpha Core Core NumAlpha 168.34 ms 0.7185 ms 0.6369 ms 1.42 0.01
Cs_NumAlpha Core Core NumAlpha 118.27 ms 1.1111 ms 0.9849 ms 1.00 0.00
Original_NumAlpha Core Core NumAlpha 130.85 ms 2.5325 ms 2.1148 ms 1.11 0.02

The code:

[<CoreJob; ClrJob;GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory);CategoriesColumn>]
type BoolPerf2() =
    let txt = System.IO.File.ReadAllText @"visualfsharp\packages\StrawberryPerl64.5.22.2.1\Tools\c\x86_64-w64-mingw32\include\mshtml.h"
    let len = txt.Length - 1
    [<Benchmark; BenchmarkCategory("AlphaNum")>]
    member __.Optimized_AlphaNum() = 
        for _ in 1 ..3 do
        for i in 0 .. len do
            TestOptimized.isAlphaNum txt.[i] |> ignore
    [<Benchmark; BenchmarkCategory("NumAlpha")>]
    member __.Optimized_NumAlpha() = 
        for _ in 1 ..3 do
        for i in 0 .. len do
            TestOptimized.isNumAlpha txt.[i] |> ignore
    [<Benchmark(Baseline = true); BenchmarkCategory("AlphaNum")>]
    member __.Cs_AlphaNum() = 
        for _ in 1 ..3 do
        for i in 0 .. len do
            TestCs.isAlphaNum txt.[i] |> ignore
    [<Benchmark(Baseline = true); BenchmarkCategory("NumAlpha")>]
    member __.Cs_NumAlpha() = 
        for _ in 1 ..3 do
        for i in 0 .. len do
            TestCs.isNumAlpha txt.[i] |> ignore
    [<Benchmark; BenchmarkCategory("AlphaNum")>]
    member __.Original_AlphaNum() = 
        for _ in 1 ..3 do
        for i in 0 .. len do
            Test.isAlphaNum txt.[i] |> ignore
    [<Benchmark; BenchmarkCategory("NumAlpha")>]
    member __.Original_NumAlpha() = 
        for _ in 1 ..3 do
        for i in 0 .. len do
            Test.isNumAlpha txt.[i] |> ignore


And the implementation of isAlphaNum and isNumAlpha:

``` F#
let isAlphaNum c =
    (c>='a' && c<='z') || (c >= '0' && c<='9')
let isNumAlpha c =
    (c >= '0' && c<='9') || (c>='a' && c<='z')


So the optimized version is sometime better than C# version. Depends highly on final code layout and CPU branch prediction based on input data.

@thinkbeforecoding
Copy link
Contributor

Great !

@dsyme dsyme changed the title [CompilerPerf] Fix 635: combine boolean logic Fix 635: combine boolean logic Jun 8, 2018
@cartermp cartermp added this to the 16.0 milestone Jul 21, 2018
@KevinRansom KevinRansom closed this Sep 6, 2018
@KevinRansom KevinRansom reopened this Sep 6, 2018
@KevinRansom KevinRansom reopened this Sep 12, 2018
@KevinRansom
Copy link
Member

@dsyme, is this ready to go?

@dsyme
Copy link
Contributor Author

dsyme commented Sep 13, 2018

Note to self: baselines need updating:

2018-09-13T09:18:26.3121234Z CodeGen\EmittedIL\Mutation (Mutation02.fs) -- failed
2018-09-13T09:18:26.3121812Z CodeGen\EmittedIL\Mutation (Mutation03.fs) -- failed
2018-09-13T09:18:26.3122366Z CodeGen\EmittedIL\Mutation (Mutation04.fs) -- failed
2018-09-13T09:18:26.3122948Z CodeGen\EmittedIL\Mutation (Mutation05.fs) -- failed

@dsyme
Copy link
Contributor Author

dsyme commented Sep 13, 2018

OK, this is ready to go

@thinkbeforecoding
Copy link
Contributor

thinkbeforecoding commented Sep 13, 2018 via email

@KevinRansom
Copy link
Member

@dsyme …. misery …. your last pr that I just merged stepped over these baselines.

@KevinRansom
Copy link
Member

@dsyme, I hope I resolved them right.

@KevinRansom KevinRansom merged commit 5e8352b into dotnet:master Sep 15, 2018
@Thorium
Copy link
Contributor

Thorium commented Sep 16, 2018

Maybe off-topic but the title sounds bit like what I did for LINQ-expression trees (and the same could be done for F# Exprs): https://thorium.github.io/Linq.Expression.Optimizer/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Boolean operations generated IL
7 participants