Skip to content

Conversation

@benmccann
Copy link
Member

I don't know if this is someone's coding style, but figured it was probably leftover from the TypeScript conversion. I only updated the code that gets shipped to the client and didn't bother touching the compiler code at all

@benmccann benmccann added the perf label Jan 8, 2024
@changeset-bot
Copy link

changeset-bot bot commented Jan 8, 2024

⚠️ No Changeset found

Latest commit: 0f4c7e8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jan 8, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
svelte-5-preview ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jan 9, 2024 0:29am

@gtm-nayan
Copy link
Contributor

Implicit boolean casts have a performance penalty,
https://github.com/localvoid/ivi/blob/master/docs/internals/perf.md#if-a--true--vs-if-a-

I think some of the flag checks where it is easily visible that the output is a number (& and --) can be shortened, others would probably have to be left as is

I'd rather not get into golfing until we're close to release

@benmccann
Copy link
Member Author

Hmm. I hadn't considered that. This PR makes the client folder go from 222.7 kB to 220.3 kB. That's without being compiled, minimized, etc. Although very few of these changes could be done by the minimizer, so it gives an idea of what the savings is. As a percentage, I imagine the savings would be larger when you minimize.

I'll have to do some more precise benchmarking later, but I'd image this PR is probably in the realm of 200ms savings from the JS arriving sooner on a fast 3g connection. You'd have to do an awful lot of implicit conversions to get anywhere near that.

@gtm-nayan
Copy link
Contributor

You'd have to do an awful lot of implicit conversions to get anywhere near that

Hmm, I guess it'd also reduce the decoding/parsing time somewhat, if it has a significant reduction in load times but hits perf, we could pick out hot paths to revert later

@benmccann
Copy link
Member Author

I just tested this in the preview site and the difference is much smaller when gzipped. It's still hard to tell what the difference would be in an actual site because the preview site doesn't bundle or minimize the files. With minimization the difference would be a bit smaller and with bundling the difference would very likely be larger since you wouldn't load the files in parallel though it'd also compress more efficiently. So I'm guessing the difference in bandwidth is probably somewhere in the 10-40ms range. I'm guessing this PR would still be a win most of the time unless you've got a really compute heavy task. Maybe @trueadm could check whether it affects js-framework-benchmark

@trueadm
Copy link
Contributor

trueadm commented Jan 9, 2024

I'm not super happy with the changes. If we are going to code-golf, then we should do so without impacting performance.

FWIW Preact undertook this path and they incurred a performance penalties from doing so, however they deemed it was worth it to meet their 3kb target. I wonder if @localvoid has opinions on the impact of these changes too.

If we are to code-golf, then maybe we can leverage utility functions for these checks rather than making things implicit boolean casts. i.e. rather than doing (some_flags & foo) !==0 you can do not_equal_zero(some_flag & foo) which will minify really well too.

@dummdidumm
Copy link
Member

If we are to code-golf, then maybe we can leverage utility functions for these checks

Will the overhead of a function call really be faster than the implicit conversion?

@trueadm
Copy link
Contributor

trueadm commented Jan 9, 2024

@dummdidumm It can be, however in the case of flags checks it looks to be almost as fast to do !! conversion. For fields that can be null though, it's always faster to use a utility function (like is_null, is_not_null, is_zero and is_not_zero) or be explicit inline, as the perf cliff is quite large here.

https://esbench.com/bench/659d31b27ff73700a4debf32

@Rich-Harris
Copy link
Member

I'd be amazed if those utility functions were worth it after gzip compared to just doing the comparison directly.

In general I think I prefer the more explicit thing (x === 0 rather than !x) anyway, the intent is much clearer

@benmccann
Copy link
Member Author

I don't know how stable these benchmark results are or how many times you have to run it to be assured of the results. I ran it three times and got three different orderings - though it seemed that the function call generally made things slower.

I copied it into jsbench.me, which runs more iterations and they were all basically equivalent:

Screenshot from 2024-01-09 09-59-17

So I think I'd optimize for size and readability over execution speed. Readability is somewhat subjective. My personal default is to write code in this implicit manner, which is why the extra code for the checks jumped out at me, but I know rich said he prefers the more explicit version. In terms of size though, I think this implicit version is pretty clearly the winner and while a few hundred bytes isn't enormous, just a couple changes like this add up to enough latency that users can tell the difference.

*/
function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, reconcile_fn) {
const is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
const is_controlled = !!(flags & EACH_IS_CONTROLLED);
Copy link
Member

Choose a reason for hiding this comment

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

If is_controlled is only ever used in contexts where its truthiness matters rather than it being true or false (I don't know whether this is the case), then we could remove the !! as well.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see now this was referenced in #10124 (comment) - Perhaps we don't want to make that change.

Copy link
Member Author

Choose a reason for hiding this comment

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

usually where I left !! it was to appease typescript in a quick an minimal way

Choose a reason for hiding this comment

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

@benmccann Have you tested Boolean(flags & EACH_IS_CONTROLLED) both in the runtime micro benchmarks and the gziped size? In term of readability I find the intent much more explicit that the !!() conversion, but I don't know about performance.

Copy link
Contributor

Choose a reason for hiding this comment

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

To be honest the biggest culprit for perf in my findings wasn't the boolean conversion on flags, but rather on null values. If you look at the bytecode generated by V8, it's almost twice as many operations needed when doing boolean conversion on these fields (interestingly). So I'd be more leaning towards having an is_null function there, which is readable and also very fast.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm. But the "hybrid" option from the benchmark is consistently the slowest or 2nd slowest one for me, so I'm not sure I buy that it's faster and it's certainly much harder to write

Copy link
Contributor

Choose a reason for hiding this comment

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

It was the fastest for me consistently with the first one. I also find it fine to write – I don't find writing explicit code to be of any benefit, especially if the type system isn't there to help you.

@localvoid
Copy link

We can just look at the generated bytecode and assembly:

function basic(i) {
  if (i & 1) {
    return 10;
  }
  return 20;
}
function negate(i) {
  if (!!(i & 1)) {
    return 10;
  }
  return 20;
}
function strictEq(i) {
  if ((i & 1) !== 0) {
    return 10;
  }
  return 20;
}

Bytecode for basic and negate functions are exactly the same:

  146 S> 0x312d5811bf96 @    0 : 0b 03             Ldar a0
  152 E> 0x312d5811bf98 @    2 : 4c 01 00          BitwiseAndSmi [1], [0]
         0x312d5811bf9b @    5 : 97 05             JumpIfToBooleanFalse [5] (0x312d5811bfa0 @ 10)
  163 S> 0x312d5811bf9d @    7 : 0d 0a             LdaSmi [10]
  173 S> 0x312d5811bf9f @    9 : a9                Return
  180 S> 0x312d5811bfa0 @   10 : 0d 14             LdaSmi [20]
  190 S> 0x312d5811bfa2 @   12 : a9                Return

strictEq bytecode:

  292 S> 0x115dafadbf96 @    0 : 0b 03             Ldar a0
  299 E> 0x115dafadbf98 @    2 : 4c 01 00          BitwiseAndSmi [1], [0]
         0x115dafadbf9b @    5 : c4                Star0
         0x115dafadbf9c @    6 : 0c                LdaZero
  304 E> 0x115dafadbf9d @    7 : 6c fa 01          TestEqualStrict r0, [1]
         0x115dafadbfa0 @   10 : 98 05             JumpIfTrue [5] (0x115dafadbfa5 @ 15)
  317 S> 0x115dafadbfa2 @   12 : 0d 0a             LdaSmi [10]
  327 S> 0x115dafadbfa4 @   14 : a9                Return
  334 S> 0x115dafadbfa5 @   15 : 0d 14             LdaSmi [20]
  344 S> 0x115dafadbfa7 @   17 : a9                Return

And after JIT compiler optimizations, all this functions will produce extactly the same assembly:

--- Optimized code ---
optimization_id = 0
source_position = 138
kind = TURBOFAN
name = basic
stack_slots = 6
compiler = turbofan
address = 0x16908f75c219

Instructions (size = 172)
0x7fe209fc7680     0  488b59f8             REX.W movq rbx,[rcx-0x8]
0x7fe209fc7684     4  f6433501             testb [rbx+0x35],0x1
0x7fe209fc7688     8  0f85325be41f         jnz 0x7fe229e0d1c0  (CompileLazyDeoptimizedCode)    ;; near builtin entry
0x7fe209fc768e     e  55                   push rbp
0x7fe209fc768f     f  4889e5               REX.W movq rbp,rsp
0x7fe209fc7692    12  56                   push rsi
0x7fe209fc7693    13  57                   push rdi
0x7fe209fc7694    14  50                   push rax
0x7fe209fc7695    15  4883ec08             REX.W subq rsp,0x8
0x7fe209fc7699    19  488975e0             REX.W movq [rbp-0x20],rsi
0x7fe209fc769d    1d  493b65a0             REX.W cmpq rsp,[r13-0x60] (external value (StackGuard::address_of_jslimit()))
0x7fe209fc76a1    21  0f864e000000         jna 0x7fe209fc76f5  <+0x75>
0x7fe209fc76a7    27  488b5518             REX.W movq rdx,[rbp+0x18]
0x7fe209fc76ab    2b  48c1fa20             REX.W sarq rdx, 32
0x7fe209fc76af    2f  488b4d18             REX.W movq rcx,[rbp+0x18]
0x7fe209fc76b3    33  f6c101               testb rcx,0x1
0x7fe209fc76b6    36  0f8565000000         jnz 0x7fe209fc7721  <+0xa1>
0x7fe209fc76bc    3c  f6c201               testb rdx,0x1
0x7fe209fc76bf    3f  0f8524000000         jnz 0x7fe209fc76e9  <+0x69>
0x7fe209fc76c5    45  48b80000000014000000 REX.W movq rax,0x1400000000
0x7fe209fc76cf    4f  488b4de8             REX.W movq rcx,[rbp-0x18]
0x7fe209fc76d3    53  488be5               REX.W movq rsp,rbp
0x7fe209fc76d6    56  5d                   pop rbp
0x7fe209fc76d7    57  4883f902             REX.W cmpq rcx,0x2
0x7fe209fc76db    5b  7f03                 jg 0x7fe209fc76e0  <+0x60>
0x7fe209fc76dd    5d  c21000               ret 0x10
0x7fe209fc76e0    60  415a                 pop r10
0x7fe209fc76e2    62  488d24cc             REX.W leaq rsp,[rsp+rcx*8]
0x7fe209fc76e6    66  4152                 push r10
0x7fe209fc76e8    68  c3                   retl
0x7fe209fc76e9    69  48b8000000000a000000 REX.W movq rax,0xa00000000
0x7fe209fc76f3    73  ebda                 jmp 0x7fe209fc76cf  <+0x4f>
0x7fe209fc76f5    75  48ba0000000010000000 REX.W movq rdx,0x1000000000
0x7fe209fc76ff    7f  52                   push rdx
0x7fe209fc7700    80  48bb70734f0100000000 REX.W movq rbx,0x14f7370
0x7fe209fc770a    8a  b801000000           movl rax,0x1
0x7fe209fc770f    8f  48bef911c4a002140000 REX.W movq rsi,0x1402a0c411f9    ;; object: 0x1402a0c411f9 <NativeContext[280]>
0x7fe209fc7719    99  e8a227ed1f           call 0x7fe229e99ec0  (CEntry_Return1_ArgvOnStack_NoBuiltinExit)    ;; near builtin entry
0x7fe209fc771e    9e  eb87                 jmp 0x7fe209fc76a7  <+0x27>
0x7fe209fc7720    a0  90                   nop
0x7fe209fc7721    a1  41ff55c8             call [r13-0x38]    ;; debug: deopt position, script offset '98'
                                                             ;; debug: deopt position, inlining id 'ffffffff'
                                                             ;; debug: deopt reason 'not a Smi'
                                                             ;; debug: deopt index 0
0x7fe209fc7725    a5  41ff55d0             call [r13-0x30]    ;; debug: deopt position, script offset '8a'
                                                             ;; debug: deopt position, inlining id 'ffffffff'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 1
0x7fe209fc7729    a9  0f1f00               nop

So, in use cases like if (a & b) { ... } I would prefer an implicit version.

In use cases when we are passing boolean values as arguments to a function and don't want to rely on inlining heuristics, I would prefer to use explicit checks like if (a === true) { ... } to avoid toBool() conversions:

function explicit(i) {
  if (i === true) {
    return 10;
  }
  return 20;
}
function implicit(i) {
  if (i) {
    return 10;
  }
  return 20;
}

Bytecode for explicit:

  166 S> 0x38f1c32dbe96 @    0 : 11                LdaTrue
         0x38f1c32dbe97 @    1 : 1c 03             TestReferenceEqual a0
         0x38f1c32dbe99 @    3 : 99 05             JumpIfFalse [5] (0x38f1c32dbe9e @ 8)
  188 S> 0x38f1c32dbe9b @    5 : 0d 0a             LdaSmi [10]
  198 S> 0x38f1c32dbe9d @    7 : a9                Return
  205 S> 0x38f1c32dbe9e @    8 : 0d 14             LdaSmi [20]
  215 S> 0x38f1c32dbea0 @   10 : a9                Return

Bytecode for implicit:

  243 S> 0xba85119be96 @    0 : 0b 03             Ldar a0
         0xba85119be98 @    2 : 97 05             JumpIfToBooleanFalse [5] (0xba85119be9d @ 7)
  256 S> 0xba85119be9a @    4 : 0d 0a             LdaSmi [10]
  266 S> 0xba85119be9c @    6 : a9                Return
  273 S> 0xba85119be9d @    7 : 0d 14             LdaSmi [20]
  283 S> 0xba85119be9f @    9 : a9                Return

Generated assembly for explicit:

Instructions (size = 156)
0x7f366afc7680     0  488b59f8             REX.W movq rbx,[rcx-0x8]
0x7f366afc7684     4  f6433501             testb [rbx+0x35],0x1
0x7f366afc7688     8  0f85325be41f         jnz 0x7f368ae0d1c0  (CompileLazyDeoptimizedCode)    ;; near builtin entry
0x7f366afc768e     e  55                   push rbp
0x7f366afc768f     f  4889e5               REX.W movq rbp,rsp
0x7f366afc7692    12  56                   push rsi
0x7f366afc7693    13  57                   push rdi
0x7f366afc7694    14  50                   push rax
0x7f366afc7695    15  4883ec08             REX.W subq rsp,0x8
0x7f366afc7699    19  493b65a0             REX.W cmpq rsp,[r13-0x60] (external value (StackGuard::address_of_jslimit()))
0x7f366afc769d    1d  0f8641000000         jna 0x7f366afc76e4  <+0x64>
0x7f366afc76a3    23  488b5518             REX.W movq rdx,[rbp+0x18]
0x7f366afc76a7    27  493995a8010000       REX.W cmpq [r13+0x1a8] (root (true_value)),rdx
0x7f366afc76ae    2e  0f8424000000         jz 0x7f366afc76d8  <+0x58>
0x7f366afc76b4    34  48b80000000014000000 REX.W movq rax,0x1400000000
0x7f366afc76be    3e  488b4de8             REX.W movq rcx,[rbp-0x18]
0x7f366afc76c2    42  488be5               REX.W movq rsp,rbp
0x7f366afc76c5    45  5d                   pop rbp
0x7f366afc76c6    46  4883f902             REX.W cmpq rcx,0x2
0x7f366afc76ca    4a  7f03                 jg 0x7f366afc76cf  <+0x4f>
0x7f366afc76cc    4c  c21000               ret 0x10
0x7f366afc76cf    4f  415a                 pop r10
0x7f366afc76d1    51  488d24cc             REX.W leaq rsp,[rsp+rcx*8]
0x7f366afc76d5    55  4152                 push r10
0x7f366afc76d7    57  c3                   retl
0x7f366afc76d8    58  48b8000000000a000000 REX.W movq rax,0xa00000000
0x7f366afc76e2    62  ebda                 jmp 0x7f366afc76be  <+0x3e>
0x7f366afc76e4    64  48ba0000000010000000 REX.W movq rdx,0x1000000000
0x7f366afc76ee    6e  52                   push rdx
0x7f366afc76ef    6f  488975e0             REX.W movq [rbp-0x20],rsi
0x7f366afc76f3    73  48bb70734f0100000000 REX.W movq rbx,0x14f7370
0x7f366afc76fd    7d  b801000000           movl rax,0x1
0x7f366afc7702    82  48baf911e45f3b0d0000 REX.W movq rdx,0xd3b5fe411f9    ;; object: 0x0d3b5fe411f9 <NativeContext[280]>
0x7f366afc770c    8c  488bf2               REX.W movq rsi,rdx
0x7f366afc770f    8f  e8ac27ed1f           call 0x7f368ae99ec0  (CEntry_Return1_ArgvOnStack_NoBuiltinExit)    ;; near builtin entry
0x7f366afc7714    94  eb8d                 jmp 0x7f366afc76a3  <+0x23>
0x7f366afc7716    96  90                   nop
0x7f366afc7717    97  41ff55d0             call [r13-0x30]    ;; debug: deopt position, script offset '9e'
                                                             ;; debug: deopt position, inlining id 'ffffffff'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 0
0x7f366afc771b    9b  90                   nop

Generated assembly for implicit:

Instructions (size = 260)
0x7efdea7c7680     0  488b59f8             REX.W movq rbx,[rcx-0x8]
0x7efdea7c7684     4  f6433501             testb [rbx+0x35],0x1
0x7efdea7c7688     8  0f85325be41f         jnz 0x7efe0a60d1c0  (CompileLazyDeoptimizedCode)    ;; near builtin entry
0x7efdea7c768e     e  55                   push rbp
0x7efdea7c768f     f  4889e5               REX.W movq rbp,rsp
0x7efdea7c7692    12  56                   push rsi
0x7efdea7c7693    13  57                   push rdi
0x7efdea7c7694    14  50                   push rax
0x7efdea7c7695    15  4883ec08             REX.W subq rsp,0x8
0x7efdea7c7699    19  493b65a0             REX.W cmpq rsp,[r13-0x60] (external value (StackGuard::address_of_jslimit()))
0x7efdea7c769d    1d  0f867f000000         jna 0x7efdea7c7722  <+0xa2>
0x7efdea7c76a3    23  488b5518             REX.W movq rdx,[rbp+0x18]
0x7efdea7c76a7    27  f6c201               testb rdx,0x1
0x7efdea7c76aa    2a  0f84a7000000         jz 0x7efdea7c7757  <+0xd7>
0x7efdea7c76b0    30  493995b0010000       REX.W cmpq [r13+0x1b0] (root (false_value)),rdx
0x7efdea7c76b7    37  0f8459000000         jz 0x7efdea7c7716  <+0x96>
0x7efdea7c76bd    3d  493995b8010000       REX.W cmpq [r13+0x1b8] (root (empty_string)),rdx
0x7efdea7c76c4    44  0f844c000000         jz 0x7efdea7c7716  <+0x96>
0x7efdea7c76ca    4a  488b4aff             REX.W movq rcx,[rdx-0x1]
0x7efdea7c76ce    4e  f6410d10             testb [rcx+0xd],0x10
0x7efdea7c76d2    52  0f853e000000         jnz 0x7efdea7c7716  <+0x96>
0x7efdea7c76d8    58  49398d38020000       REX.W cmpq [r13+0x238] (root (heap_number_map)),rcx
0x7efdea7c76df    5f  0f8484000000         jz 0x7efdea7c7769  <+0xe9>
0x7efdea7c76e5    65  49398db0020000       REX.W cmpq [r13+0x2b0] (root (bigint_map)),rcx
0x7efdea7c76ec    6c  0f846c000000         jz 0x7efdea7c775e  <+0xde>
0x7efdea7c76f2    72  48b8000000000a000000 REX.W movq rax,0xa00000000
0x7efdea7c76fc    7c  488b4de8             REX.W movq rcx,[rbp-0x18]
0x7efdea7c7700    80  488be5               REX.W movq rsp,rbp
0x7efdea7c7703    83  5d                   pop rbp
0x7efdea7c7704    84  4883f902             REX.W cmpq rcx,0x2
0x7efdea7c7708    88  7f03                 jg 0x7efdea7c770d  <+0x8d>
0x7efdea7c770a    8a  c21000               ret 0x10
0x7efdea7c770d    8d  415a                 pop r10
0x7efdea7c770f    8f  488d24cc             REX.W leaq rsp,[rsp+rcx*8]
0x7efdea7c7713    93  4152                 push r10
0x7efdea7c7715    95  c3                   retl
0x7efdea7c7716    96  48b80000000014000000 REX.W movq rax,0x1400000000
0x7efdea7c7720    a0  ebda                 jmp 0x7efdea7c76fc  <+0x7c>
0x7efdea7c7722    a2  48ba0000000010000000 REX.W movq rdx,0x1000000000
0x7efdea7c772c    ac  52                   push rdx
0x7efdea7c772d    ad  488975e0             REX.W movq [rbp-0x20],rsi
0x7efdea7c7731    b1  48bb70734f0100000000 REX.W movq rbx,0x14f7370
0x7efdea7c773b    bb  b801000000           movl rax,0x1
0x7efdea7c7740    c0  48baf91148f691370000 REX.W movq rdx,0x3791f64811f9    ;; object: 0x3791f64811f9 <NativeContext[280]>
0x7efdea7c774a    ca  488bf2               REX.W movq rsi,rdx
0x7efdea7c774d    cd  e86e27ed1f           call 0x7efe0a699ec0  (CEntry_Return1_ArgvOnStack_NoBuiltinExit)    ;; near builtin entry
0x7efdea7c7752    d2  e94cffffff           jmp 0x7efdea7c76a3  <+0x23>
0x7efdea7c7757    d7  4885d2               REX.W testq rdx,rdx
0x7efdea7c775a    da  74ba                 jz 0x7efdea7c7716  <+0x96>
0x7efdea7c775c    dc  eb94                 jmp 0x7efdea7c76f2  <+0x72>
0x7efdea7c775e    de  f74207feffff7f       testl [rdx+0x7],0x7ffffffe
0x7efdea7c7765    e5  758b                 jnz 0x7efdea7c76f2  <+0x72>
0x7efdea7c7767    e7  ebad                 jmp 0x7efdea7c7716  <+0x96>
0x7efdea7c7769    e9  c5fb104207           vmovsd xmm0,[rdx+0x7]
0x7efdea7c776e    ee  c5f157c9             vxorpd xmm1,xmm1,xmm1
0x7efdea7c7772    f2  c5f92ec8             vucomisd xmm1,xmm0
0x7efdea7c7776    f6  0f8576ffffff         jnz 0x7efdea7c76f2  <+0x72>
0x7efdea7c777c    fc  eb98                 jmp 0x7efdea7c7716  <+0x96>
0x7efdea7c777e    fe  90                   nop
0x7efdea7c777f    ff  41ff55d0             call [r13-0x30]    ;; debug: deopt position, script offset 'eb'
                                                             ;; debug: deopt position, inlining id 'ffffffff'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 0
0x7efdea7c7783   103  90                   nop

In use cases like if (fn()) { ... } when we can assume that JIT compiler will be able to inline fn() function and infer that its return type is boolean, I would prefer an implicit version.

I don't think that there should be a one-size-fits-all rule for comparisons, it should be evaluated for each case individually.

@trueadm
Copy link
Contributor

trueadm commented Jan 10, 2024

@localvoid Thanks for the awesome response. To clarify too, with if (expression === true) it seems better to have that than if (expression) even if we always know expression is a boolean? I would have assumed the fast-path jump would have made it the same amount of instructions for when it is a boolean. I found that doing if (expression) where expression is a non-boolean value to fall down the slow path you mention above, as we do a lot of is not null and null checks everywhere and making them implicit to have an impact.

@trueadm
Copy link
Contributor

trueadm commented Jan 11, 2024

I think we can maybe do the flags checks using shorter boolean coercion (without the !!) but I don't think we should land the other changes until Svelte 5 is more stable and things are less in flux as it will just cause churn. The reason we're explicit about things being === true or === null isn't for performance alone, it's for correctness. Especially when you run into a value that might be "" or 0 and suddenly things start failing.

@benmccann
Copy link
Member Author

The reason we're explicit about things being === true or === null isn't for performance alone, it's for correctness. Especially when you run into a value that might be "" or 0 and suddenly things start failing.

I made sure to review each change on a case-by-case basis rather than doing a find and replace for this reason. The vast majority of null comparisons were to signals or DOM nodes where we didn't have to worry about other falsy values like "" or 0, so I don't think this change is very likely to cause issues. And I actually think it's better to get it out of the way and create an agreed upon coding style while things are in flux because if it does inadvertently cause some issue - well people expect that right now as we're in alpha.

@Rich-Harris
Copy link
Member

Given the concerns about performance regressions, and the fact that this code is harder to understand (since if (!x) just isn't as obvious as if (x === 0), etc), I'm going to close this. If we're going to make these sorts of changes they really should be accompanied by measurements rather than guesswork

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants