1
0
Fork
You've already forked casefold
1

Performance Questions #1

Closed
opened 2026年05月23日 19:04:48 +02:00 by halostatue · 4 comments
Contributor
Copy link

Hi. I'm working on a project where I've implemented is_alpha, is_hex, etc. and was wondering if there was an existing project that provided this functionality and I found casefold.

The implementation you have for is_alpha and similar functions is at least 3-7 times slower than an optimized version would be on all matching or mixed (the performance is the same for all invalid because list.all short circuits).

It would likely be beneficial to provide _char versions of these functions as well when someone is dealing with grapheme values already (I'm doing so with pattern matching in my parser) so that the overhead of list.all and string.to_graphemes is avoided if those expenses have already been paid.

Benchmark
//// Comparative benchmark: internal is_hex_digit vs casefold's contains
//// approach
importgleam/intimportgleam/ioimportgleam/listimportgleam/stringimportgleamy/benchconsthex="0123456789abcdefABCDEF"constnon_hex="ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ"pubfnmain(){lethex_chars=string.to_graphemes(hex)letnon_chars=string.to_graphemes(non_hex)let#(valid,invalid,mixed)=int.range(from:1,to:100,with:#([],[],[]),run:fn(acc,i){let#(va,ia,ma)=accletmixed=caseint.random(6){vifv<3->build_sample(hex_chars,i)_->build_sample(non_chars,i)}#([build_sample(hex_chars,i),..va],[build_sample(non_chars,i),..ia],[mixed,..ma])})bench.run([bench.Input("all valid",valid),bench.Input("all invalid",invalid),bench.Input("mixed",mixed),],[bench.Function("is_hex_case * 1000",bench.repeat(1000,fn(lists){list.each(lists,is_hex_case)}),),bench.Function("is_hex_contains * 1000",bench.repeat(1000,fn(lists){list.each(lists,is_hex_contains)}),),],[bench.Duration(2000),bench.Warmup(500)],)|>bench.table([bench.IPS,bench.Min,bench.Mean,bench.P(99)])|>io.println}fnis_hex_case(s:String)->Bool{string.to_graphemes(s)|>list.all(fn(ch){casech{"0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"a"|"b"|"c"|"d"|"e"|"f"|"A"|"B"|"C"|"D"|"E"|"F"->True_->False}})}fnis_hex_contains(s:String)->Bool{string.to_graphemes(s)|>list.all(fn(ch){string.contains(hex,ch)})}fnbuild_sample(pool:List(String),i:Int)->String{list.sample(pool,i)|>string.join("")}
Hi. I'm working on a project where I've implemented `is_alpha`, `is_hex`, etc. and was wondering if there was an existing project that provided this functionality and I found casefold. The implementation you have for `is_alpha` and similar functions is at least 3-7 times slower than an optimized version would be on all matching or mixed (the performance is the same for all invalid because `list.all` short circuits). It would likely be beneficial to provide `_char` versions of these functions as well when someone is dealing with grapheme values already (I'm doing so with pattern matching in my parser) so that the overhead of `list.all` and `string.to_graphemes` is avoided if those expenses have already been paid. <details><summary>Benchmark</summary> ```gleam //// Comparative benchmark: internal is_hex_digit vs casefold's contains //// approach import gleam/int import gleam/io import gleam/list import gleam/string import gleamy/bench const hex = "0123456789abcdefABCDEF" const non_hex = "ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ" pub fn main() { let hex_chars = string.to_graphemes(hex) let non_chars = string.to_graphemes(non_hex) let #(valid, invalid, mixed) = int.range(from: 1, to: 100, with: #([], [], []), run: fn(acc, i) { let #(va, ia, ma) = acc let mixed = case int.random(6) { v if v < 3 -> build_sample(hex_chars, i) _ -> build_sample(non_chars, i) } #([build_sample(hex_chars, i), ..va], [build_sample(non_chars, i), ..ia], [ mixed, ..ma ]) }) bench.run( [ bench.Input("all valid", valid), bench.Input("all invalid", invalid), bench.Input("mixed", mixed), ], [ bench.Function( "is_hex_case * 1000", bench.repeat(1000, fn(lists) { list.each(lists, is_hex_case) }), ), bench.Function( "is_hex_contains * 1000", bench.repeat(1000, fn(lists) { list.each(lists, is_hex_contains) }), ), ], [bench.Duration(2000), bench.Warmup(500)], ) |> bench.table([bench.IPS, bench.Min, bench.Mean, bench.P(99)]) |> io.println } fn is_hex_case(s: String) -> Bool { string.to_graphemes(s) |> list.all(fn(ch) { case ch { "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" -> True _ -> False } }) } fn is_hex_contains(s: String) -> Bool { string.to_graphemes(s) |> list.all(fn(ch) { string.contains(hex, ch) }) } fn build_sample(pool: List(String), i: Int) -> String { list.sample(pool, i) |> string.join("") } ``` </details>
Owner
Copy link

Hi!

I would be really happy to accept patches to improve performance, I haven’t looked at that at all (for my own use case performance has not been an issue).

Hi! I would be really happy to accept patches to improve performance, I haven’t looked at that at all (for my own use case performance has not been an issue).
Author
Contributor
Copy link

I have a queue right now of ~3 projects (the current one, the one that needs the current one, and then the one that needs that one), but I'll look at submitting some PRs. Do you have an issue with adding new functions like is_octal_digit and is_octal as well?.

I have a queue right now of ~3 projects (the current one, the one that *needs* the current one, and then the one that needs *that* one), but I'll look at submitting some PRs. Do you have an issue with adding new functions like `is_octal_digit` and `is_octal` as well?.
Owner
Copy link

No, that would be great too! And no pressure, take your time.

No, that would be great too! And no pressure, take your time.
Author
Contributor
Copy link

Resolved in #2.

Resolved in #2.
Sign in to join this conversation.
No Branch/Tag specified
main
v2.1.0
v2.0.1
v2.0.0
v1.6.0
v1.5.0
v1.4.0
v1.3.1
v1.3.0
v1.2.0
v1.1.0
v1.0.0
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
krig/casefold#1
Reference in a new issue
krig/casefold
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?