On Tue, 20 Mar 2018, Chris Cunningham wrote:
> Hi. Reopening this thread - probably tomorrow will implement 'better' solution (with documentation). > > On Sat, Feb 10, 2018 at 12:03 PM, Tobias Pape <[hidden email]> wrote: > > > On 10.02.2018, at 20:36, Tony Garnock-Jones <[hidden email]> wrote: > > > > On 02/10/2018 07:02 PM, Levente Uzonyi wrote: > >> So, > >> a perform: {#or:. #xor:. #and:} atRandom with: b > >> would just work for that imaginary interpreter if b were a Boolean. > > > > Yes, that "interpreter" works just fine today, if b is a Boolean. It's > > the case where b is a Boolean-producing expression - such as in a "lazy" > > interpreter - that doesn't work without Fabio's proposed change. > > > > I went and looked at the ANSI standard (draft), btw [1]. > > > > There, #xor: is specified as taking only a boolean. > > > > So this would be an extension, potentially affecting portability, for > > what that's worth these days. > > > > I think the performance objection has been well-refuted, and I see the > > consistency benefit as being real, but probably pretty limited, and I > > kind of don't like the potential portability implications. > > I presume the main "feeling" here is parallelity: > > #& takes an evaluated boolean #and: takes an unevaluated block > #| takes an evaluated boolean #or: takes an unevaluated block > > #xor: takes an evaluated boolean but looks like #and: and #or:, > So it seems to belong to the right side, and then we think again about > the left side, come up with symbols, only to find that #~= and #~~ are already there. > > So, just lets go all the way and document #xor: better, not making take it a block, > maybe pointing out that #~= ist typically better fitted… > > Best regards > -Tobias > > > So, I have written some tests for speed and 'ensuring the arguments are booleans', with another proposed solution. > > First, speed: > > #xor: base > #~= is 124% slower (or 2-1/4 as much time as existing xor: method) > #~~ is 75% faster > #cbcXor: is 32% slower > > Note: for real speed, use ~~ , not ~= ! > > Why cbcXor: ? It is the only one that makes sure the arguments are boolean - fails otherwise. True >> #xor: aBoolean aBoolean ifTrue: [ ^false ] ifFalse: [ ^true ] False >> #xor: aBoolean aBoolean ifTrue: [ ^true ] ifFalse: [ ^false ] Levente > > Tests to run: > > First, install > > True>>cbcXor: boolean > ^boolean isFalse > True>>isTrue > ^true > True>>isFalse > ^false > False>>cbcXor: boolean > ^boolean isTrue > False>>isFalse > ^true > False>>isTrue > ^false > > "Setup" > pairs := #( true false true true false false false true ). > invalidPairs := { true. 1. true. #[ 1 3 0 9 ]. true. 'abc'. false. 1. false. #[ 1 3 0 9 ]. false. 'abc'. 'abc'. true. #[ 1 3 0 9 ]. false. }. > methods := { > [:f :s| ]. > [:f :s| f xor: s]. > [:f :s| f cbcXor: s]. > [:f :s| f ~= s]. > [:f :s| f ~~ s]. > }. > "Validity Test" > validCheck := methods collect: [:m| > { > m sourceString. > #( true false false true ) = (pairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]) > ifTrue: ['Valid'] ifFalse: ['ERRORS']. > pairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]. > }]. > "all methods are valid" > "Testing that non-booleans actually result in errors, and not false positive/negatives" > invalidCheck := methods collect: [:m| > { > m sourceString. > ((invalidPairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]) select: [:r| r = #error]) size = 8 > ifTrue: ['Valid'] ifFalse: ['ERRORS']. > invalidPairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]. > }]. > "Only #cbcXor: correctly fails all of these. Some interesting results..." > "Timing test. Need to run 10,000,000 to get reasonable distinctions on my machine." > timing := methods collect: [:m| > { m sourceString. [[10000000 timesRepeat: [pairs pairsDo: m]] timeToRun] on: Error do: [#invalid]. } > ]. > "And showing percentage slower" > base := timing first second. > bench := timing second second - base. > timing allButFirst collect: [:res| { res first. this := res second - base. ((this - bench) * 100 / bench) rounded. }]. > > Thanks, > cbc > > |
On Wed, Mar 21, 2018 at 2:48 PM, Levente Uzonyi <[hidden email]> wrote:
and is this noticeably slower? True >> #xor: aBoolean ^aBoolean ifTrue: [ false ] ifFalse: [ true ] False >> #xor: aBoolean ^aBoolean ifTrue: [ true ] ifFalse: [ false ] but I would much prefer to see either Boolean>>xor: aBooleanOrBlock ^ aBooleanOrBlock value not or True >> #xor: aBooleanOrBlock ^aBooleanOrBlock value ifTrue: [ false ] ifFalse: [ true ] False >> #xor: aBooleanOrBlock ^aBooleanOrBlock value ifTrue: [ true ] ifFalse: [ false ] The lack of symmetry with and: and or: is, IMO, bad. Levente _,,,^..^,,,_ best, Eliot |
On Wed, Mar 21, 2018 at 4:09 PM, Eliot Miranda <[hidden email]> wrote:
I started out with this mind-set, but Tobias' arguments crystallized the discussion for me: 1. Adhere to standards (booleans only) 2. Or, maybe #xor: behave like and: and or:. I also found another discussion - unlike and: and or:, for xor: you will ALWAYS have to evaluate the block - and that doubles (or significantly more) the amount of time needed to run the method. It is still small, of course. With this last slight variation, our rule would be "as long as the receiver is a boolean and the arguments' value is a boolean, then we apply the rule". I could live with that. -cbc
|
In reply to this post by Eliot Miranda-2
On Wed, 21 Mar 2018, Eliot Miranda wrote:
> > > On Wed, Mar 21, 2018 at 2:48 PM, Levente Uzonyi <[hidden email]> wrote: > On Tue, 20 Mar 2018, Chris Cunningham wrote: > > Hi. Reopening this thread - probably tomorrow will implement 'better' solution (with documentation). > > On Sat, Feb 10, 2018 at 12:03 PM, Tobias Pape <[hidden email]> wrote: > > > On 10.02.2018, at 20:36, Tony Garnock-Jones <[hidden email]> wrote: > > > > On 02/10/2018 07:02 PM, Levente Uzonyi wrote: > >> So, > >> a perform: {#or:. #xor:. #and:} atRandom with: b > >> would just work for that imaginary interpreter if b were a Boolean. > > > > Yes, that "interpreter" works just fine today, if b is a Boolean. It's > > the case where b is a Boolean-producing expression - such as in a "lazy" > > interpreter - that doesn't work without Fabio's proposed change. > > > > I went and looked at the ANSI standard (draft), btw [1]. > > > > There, #xor: is specified as taking only a boolean. > > > > So this would be an extension, potentially affecting portability, for > > what that's worth these days. > > > > I think the performance objection has been well-refuted, and I see the > > consistency benefit as being real, but probably pretty limited, and I > > kind of don't like the potential portability implications. > > I presume the main "feeling" here is parallelity: > > #& takes an evaluated boolean #and: takes an unevaluated block > #| takes an evaluated boolean #or: takes an unevaluated block > > #xor: takes an evaluated boolean but looks like #and: and #or:, > So it seems to belong to the right side, and then we think again about > the left side, come up with symbols, only to find that #~= and #~~ are already there. > > So, just lets go all the way and document #xor: better, not making take it a block, > maybe pointing out that #~= ist typically better fitted… > > Best regards > -Tobias > > > So, I have written some tests for speed and 'ensuring the arguments are booleans', with another proposed solution. > > First, speed: > > #xor: base > #~= is 124% slower (or 2-1/4 as much time as existing xor: method) > #~~ is 75% faster > #cbcXor: is 32% slower > > Note: for real speed, use ~~ , not ~= ! > > Why cbcXor: ? It is the only one that makes sure the arguments are boolean - fails otherwise. > > > Here is a simpler and faster alternative: > > True >> #xor: aBoolean > > aBoolean ifTrue: [ ^false ] ifFalse: [ ^true ] > > False >> #xor: aBoolean > > aBoolean ifTrue: [ ^true ] ifFalse: [ ^false ] > > > and is this noticeably slower? > > True >> #xor: aBoolean > > ^aBoolean ifTrue: [ false ] ifFalse: [ true ] > > False >> #xor: aBoolean > > ^aBoolean ifTrue: [ true ] ifFalse: [ false ] 33 <10> pushTemp: 0 34 <99> jumpFalse: 37 35 <71> pushConstant: true 36 <90> jumpTo: 38 37 <72> pushConstant: false 38 <7C> returnTop And for my variant: 33 <10> pushTemp: 0 34 <98> jumpFalse: 36 35 <79> return: true 36 <7A> return: false I measured the latter to be 14% faster. Levente > > but I would much prefer to see either > > Boolean>>xor: aBooleanOrBlock > > ^ aBooleanOrBlock value not > > or > > True >> #xor: aBooleanOrBlock > > ^aBooleanOrBlock value ifTrue: [ false ] ifFalse: [ true ] > > False >> #xor: aBooleanOrBlock > > ^aBooleanOrBlock value ifTrue: [ true ] ifFalse: [ false ] > > The lack of symmetry with and: and or: is, IMO, bad. > > > Levente > > > Tests to run: > > First, install > > True>>cbcXor: boolean > ^boolean isFalse > True>>isTrue > ^true > True>>isFalse > ^false > False>>cbcXor: boolean > ^boolean isTrue > False>>isFalse > ^true > False>>isTrue > ^false > > "Setup" > pairs := #( true false true true false false false true ). > invalidPairs := { true. 1. true. #[ 1 3 0 9 ]. true. 'abc'. false. 1. false. #[ 1 3 0 9 ]. false. 'abc'. 'abc'. true. #[ 1 3 0 9 ]. false. }. > methods := { > [:f :s| ]. > [:f :s| f xor: s]. > [:f :s| f cbcXor: s]. > [:f :s| f ~= s]. > [:f :s| f ~~ s]. > }. > "Validity Test" > validCheck := methods collect: [:m| > { > m sourceString. > #( true false false true ) = (pairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]) > ifTrue: ['Valid'] ifFalse: ['ERRORS']. > pairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]. > }]. > "all methods are valid" > "Testing that non-booleans actually result in errors, and not false positive/negatives" > invalidCheck := methods collect: [:m| > { > m sourceString. > ((invalidPairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]) select: [:r| r = #error]) size = 8 > ifTrue: ['Valid'] ifFalse: ['ERRORS']. > invalidPairs pairsCollect: [:a :b| [m value: a value: b] on: Error do: [#error]]. > }]. > "Only #cbcXor: correctly fails all of these. Some interesting results..." > "Timing test. Need to run 10,000,000 to get reasonable distinctions on my machine." > timing := methods collect: [:m| > { m sourceString. [[10000000 timesRepeat: [pairs pairsDo: m]] timeToRun] on: Error do: [#invalid]. } > ]. > "And showing percentage slower" > base := timing first second. > bench := timing second second - base. > timing allButFirst collect: [:res| { res first. this := res second - base. ((this - bench) * 100 / bench) rounded. }]. > > Thanks, > cbc > > > > > > > > -- > _,,,^..^,,,_ > best, Eliot > > |
On Wed, Mar 21, 2018 at 5:15 PM, Levente Uzonyi <[hidden email]> wrote:
For xor: it's hardly worth it. Nicer if the compiler and decompiler did the transformation...
_,,,^..^,,,_ best, Eliot |
So, speed: current (^aBoolean or ^aBoolean not) - standard ~~ 71% faster ul's xor: (aBoolean ifTrue: [^] ifFalse: [^]) 7% slower em's xor: ( ^aBoolean ifTrue: [] ifFalse: []) 22% slower
ul's block xor: (aBoolean value ifTrue: [^] ifFalse: [^]) 42% slower em's block xor: (^aBoolean value ifTrue: [] ifFalse: []) 65% slower fn's xor: (^
alternativeBlock value or ^alternativeBlock value not) 84% slower I find it weird that the extra ifTrue:ifFalse: call after the value actually makes the method faster. Weird. But it does make it more consistent/right - it only works when the receiver and value of the argument are both booleans - with the right results. Note that even though the percentages are fairly large, the actual time to run 10,000,000 of these checks took right about 3 second for the slowest of these - once I've removed the 6 seconds of the loop overhead. Still fast. Will be moving to trunk shortly, using this style:
^aBoolean value ifTrue: [] ifFalse: []
It just feels more natural. Feel free to adjust if desired. -cbc On Wed, Mar 21, 2018 at 7:20 PM, Eliot Miranda <[hidden email]> wrote:
|
Free forum by Nabble | Edit this page |