Skip to content

Use builtin.null_list in both list and dict is_empty - #128

Merged
KtorZ merged 2 commits into
aiken-lang:mainfrom
Micrograx:fix/use_null_list_builtin
Sep 26, 2026
Merged

KtorZ merged 2 commits into
aiken-lang:mainfrom
Micrograx:fix/use_null_list_builtin

Conversation

@Micrograx

@Micrograx Micrograx commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Small optimization for list and dict is_empty, after noticing this behavior in tests:

const l = []

// [mem:   1.13 K, cpu: 293.09 K]
test bench_is_empty() {
  list.is_empty(l)
}

// [mem:    532.0, cpu: 138.53 K]
test bench_builtin() {
  builtin.null_list(l)
}

// EDIT: Test added
// [mem:    200.0, cpu:   16.1 K]
test bench_equal(){
  l == []
}

@KtorZ

KtorZ commented May 8, 2026

Copy link
Copy Markdown
Member

Ola! Looking a bit more careful into this, I believe the right fix would be == [] since this is even faster than using the null_list builtin. But thanks for pointing this out!

@Micrograx

Copy link
Copy Markdown
Contributor Author

Well, you're right, and a lot faster actually

@KtorZ

KtorZ commented May 8, 2026

Copy link
Copy Markdown
Member

Note that the reported execution units are misleading here; I'll eventually fix this in the compiler but everytime you see:

[mem:    200.0, cpu:   16.1 K]

(precisely those numbers), it indicates that the compiler had in fact reduced the program down to True. Which happens when the program is simple and mostly composed of constant expressions. So to compare these cases correctly, you have to make the test arbitrarily more complex; enough for the compiler to not optimize everything away. For example:

use aiken/builtin

fn is_empty_when_is(self: Pairs<key, value>) -> Bool {
  when self is {
    [] -> True
    _ -> False
  }
}

fn is_empty_eq(self: Pairs<key, value>) -> Bool {
  self == []
}

fn is_empty_builtin(self: Pairs<key, value>) -> Bool {
  builtin.null_list(self)
}

const fixture: Pairs<Int, Int> = []

test bench_is_empty_when_is() {
  and {
    is_empty_when_is(fixture),
    True,
  }
}

test bench_is_empty_eq() {
  and {
    is_empty_eq(fixture),
    True,
  }
}

test bench_is_empty_builtin() {
  and {
    is_empty_builtin(fixture),
    True,
  }
}

This way, the overhead between each is constant and we can at least compare them; which still confirms the observations:

    ┍━ == ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    │ PASS [mem:   1.93 K, cpu: 497.14 K] bench_is_empty_when_is
    │ PASS [mem:   1.00 K, cpu: 220.14 K] bench_is_empty_eq
    │ PASS [mem:   1.33 K, cpu: 342.58 K] bench_is_empty_builtin
    ┕━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3 tests | 3 passed | 0 failed

@Micrograx

Copy link
Copy Markdown
Contributor Author

Ah! makes sense. Good to know the == route is still the most efficient

@KtorZ
KtorZ merged commit c562ce8 into aiken-lang:main Sep 26, 2026
10 of 11 checks passed

This branch had an error being deployed

1 failed deployment
github-pages — e2f4743f Deployed May 8, 2026 by Micrograx via deploy-docs #484
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.

2 participants