Stream: beginners

Topic: Compiler seems extremely slow on some code


view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:11):

The following main.roc code takes forever to compile, using all my CPU. It's been running for well over 10 minutes, I don't understand why. I tried removing almost everything and adding one piece at a time, and it just gets slower and slower. So I'm guessing the full code will eventually finish compiling one day, but perhaps not today. Any idea what's going on? Sorry if the code is really ugly, I'm a pebble (=a little Roc newbie).

app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }

import cli.Stdout
import cli.Task
import cli.Arg
import cli.File
import cli.Utc

import Day01
import Day02
import Day03
import Day04
import Day05
import Day06
import Day07
import Day08
import Day09
import Day10
import Day11
import Day12
import Day13
import Day14
import Day15
import Day16
import Day17
import Day18
import Day19
import Day20
import Day21
import Day22
import Day23
import Day24
import Day25

dataPath = \day ->
    "data/day$(if day < 10 then "0" else "")$(Num.toStr day).txt"

loadData = \day -> day |> dataPath |> File.readUtf8!

solutions = [
    (Day01.part1, Day01.part2),
    (Day02.part1, Day02.part2),
    (Day03.part1, Day03.part2),
    (Day04.part1, Day04.part2),
    (Day05.part1, Day05.part2),
    (Day06.part1, Day06.part2),
    (Day07.part1, Day07.part2),
    (Day08.part1, Day08.part2),
    (Day09.part1, Day09.part2),
    (Day10.part1, Day10.part2),
    (Day11.part1, Day11.part2),
    (Day12.part1, Day12.part2),
    (Day13.part1, Day13.part2),
    (Day14.part1, Day14.part2),
    (Day15.part1, Day15.part2),
    (Day16.part1, Day16.part2),
    (Day17.part1, Day17.part2),
    (Day18.part1, Day18.part2),
    (Day19.part1, Day19.part2),
    (Day20.part1, Day20.part2),
    (Day21.part1, Day21.part2),
    (Day22.part1, Day22.part2),
    (Day23.part1, Day23.part2),
    (Day24.part1, Day24.part2),
    (Day25.part1, Day25.part2),
]

runSolution = \solution, index, input ->
    Stdout.line! "Part $(Num.toStr index):"
    startTime = Utc.now!
    result = solution input
    endTime = Utc.now!
    delta = Utc.deltaAsMillis startTime endTime |> Num.toStr
    Stdout.line! "$(result) ($(delta)ms)"

checkDay = \day ->
    if day < 1 || day > 25 then Task.err (InvalidDay "Must be between 1 and 25") else Task.ok {}

runDay = \dayArg ->
    day = Str.toU64 dayArg |> Task.fromResult!
    checkDay! day
    Stdout.line! "Day $(Num.toStr day)"
    input = loadData! day
    (part1, part2) = List.get solutions (day - 1) |> Task.fromResult!
    runSolution! part1 1 input
    runSolution! part2 2 input

main =
    args = Arg.list! {}
    daysStr = if List.len args < 2 then List.range { start: At 1, end: At 25 } |> List.map Num.toStr else args |> List.dropFirst 1
    _ = daysStr |> List.map runDay |> List.reverse |> Task.sequence!
    Task.ok {}

Every DayXX.roc file contains the same thing:

module [part1, part2]
part1 = \input -> "Part 1 not implemented"
part2 = \input -> "Part 2 not implemented"

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:13):

Are you importing/ingesting file bytes? i.e. like this https://www.roc-lang.org/examples/IngestFiles/README.html

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:13):

We have a known LLVM bug that makes that ultra slow -- here is the PR to fix https://github.com/roc-lang/roc/pull/6832

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:14):

Ah thanks, yes my code does containFile.readUtf8!, is that what you mean?

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:14):

No, File.readUtf8 should be fine

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:15):

My code also uses Utc.now!, maybe that's related?

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:16):

I'm a pebble (=a little Roc newbie)

I love this, :smiley:

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:17):

Basically the code takes some days as command line arguments (e.g., roc main.roc 1 2 3), and for each day it loads the corresponding data/dayXX.txt file, and it runs DayXX.part1 and DayXX.part2, times them, and prints the results along with the times.

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:19):

I'm not seeing anything that looks obviously wrong

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:20):

So the I/O operations involved are Arg.list, Stdout.line, Utc.now, and File.readUtf8.

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:21):

I suspect we have a bug somewhere related to composing a lot of Tasks in a program

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:21):

Like we a hitting an edge case or something that is blowing up the code gen

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:22):

It could maybe be related to all the refcounting and other fixes that Brendan fixed recently. All available on the TESTING nightly and basic-cli 0.13.0 if you would like to test that

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:23):

Luke Boswell said:

I'm not seeing anything that looks obviously wrong

If you want to test this, you can remove all the import DayXX, and replace the definition of solutions with this:

solutions = [
    (\i -> i, \i -> i),
]

Then run roc main.roc 1

For me, it's still super slow.

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:23):

Luke Boswell said:

It could maybe be related to all the refcounting and other fixes that Brendan fixed recently. All available on the TESTING nightly and basic-cli 0.13.0 if you would like to test that

Ah yes, thanks, I'll give this a shot.

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:24):

Ah, ok... so if you run roc check you will see there are issues

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:25):

check exits earlier in the compiler pipeline so its giving us the problem reports, whereras build is getting stuck trying to resolve stuff that is definitely broken.

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:25):

I think you just need to add an import cli.Task exposing [Task] and that will fix your issue here

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:26):

Or maybe not...

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:27):

The only issues reported by roc check were warnings about the fact that part1 = \input -> "not implemented" does not use the variable input. I replaced this with part1 = \_ -> "not implemented" and now roc check gives me no warning or errors.

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:27):

I'm using roc nightly pre-release, built from commit 070d14a on Sat Jul 13 09:01:57 UTC 2024

view this post on Zulip Aurélien Geron (Jul 29 2024 at 11:29):

I also tried to change the import as you said, but it didn't fix the issue. I'll try basic-cli 0.13, as you suggested (probably tomorrow because it's pretty late here in Auckland). Cheers!

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:30):

So this kind of issue isn't totally uncommon. I find it's almost always trying to do some kind of alias or type analysis on broken code and stuck in a loop. So I find going through and adding type annotations to "pin" the types can help the compiler tell me where the issue is

view this post on Zulip Luke Boswell (Jul 29 2024 at 11:55):

Yeah, so there's definitely a bug in here somewhere related to composition of Tasks. It may be related to unifying error tags, or the way we are desugaring the !, or some other bug. I spent a while trying to minify it, but haven't been able to isolate it.

This is what I was using... basically just added type annotations.

app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/nW9yMRtZuCYf1Oa9vbE5XoirMwzLbtoSgv7NGhUlqYA.tar.br" }

import cli.Stdout
import cli.Task
import cli.Arg
import cli.File
import cli.Utc
import cli.Task exposing [Task]

dataPath : U64 -> Str
dataPath = \day ->
    "data/day$(if day < 10 then "0" else "")$(Num.toStr day).txt"

loadData : U64 -> Task Str _
loadData = \day ->
    day
    |> dataPath
    |> File.readUtf8
    |> Task.mapErr UnableToReadFile

solutions : List (Str -> Str, Str -> Str)
solutions = [
    (\i -> i,\i -> i),
]

runSolution : (Str -> Str), U64, Str -> Task {} []_
runSolution = \solution, index, input ->
    Stdout.line! "Part $(Num.toStr index):"
    startTime = Utc.now!
    result = solution input
    endTime = Utc.now!
    delta = Utc.deltaAsMillis startTime endTime |> Num.toStr
    Stdout.line! "$(result) ($(delta)ms)"

checkDay : U64 -> Task {} [InvalidDay Str]_
checkDay = \day ->
    if day < 1 || day > 25 then Task.err (InvalidDay "Must be between 1 and 25") else Task.ok {}

runDay : Str -> Task {} []_
runDay = \dayArg ->
    day = Str.toU64 dayArg |> Task.fromResult!
    checkDay! day
    Stdout.line! "Day $(Num.toStr day)"
    input = loadData! day
    (part1, part2) = List.get solutions (day - 1) |> Task.fromResult!
    runSolution! part1 1 input
    runSolution! part2 2 input

main =
    args = Arg.list! {}
    daysStr = if List.len args < 2 then List.range { start: At 1, end: At 25 } |> List.map Num.toStr else args |> List.dropFirst 1
    _ = daysStr |> List.map runDay |> List.reverse |> Task.sequence!
    Task.ok {}

view this post on Zulip Brendan Hansknecht (Jul 29 2024 at 15:53):

Just to clarify this, no matter the result of any extra analysis done here, this is definitely a compiler bug as well.

view this post on Zulip Luke Boswell (Jul 30 2024 at 07:46):

I'm not sure I found the root of the problem... but I certainly a fix.

I suspected the Task.sequence as I haven't really seen that used much.

I applied @Brendan Hansknecht's Task.loop implementation on the Task as builtin branch and this runs really nicely.

view this post on Zulip Luke Boswell (Jul 30 2024 at 07:48):

I'll see if the same fix also works with current basic-cli.

I'm not sure we want to make another intermediate release, I think I'd rather just upgrade to builtin-task.

view this post on Zulip Luke Boswell (Jul 30 2024 at 07:53):

Yes also works on basic-cli

view this post on Zulip Luke Boswell (Jul 30 2024 at 07:57):

Here is a draft PR with the fix https://github.com/roc-lang/basic-cli/pull/236

view this post on Zulip Luke Boswell (Jul 30 2024 at 07:58):

If anyone would like this, you can build from source by cloning basic-cli on that branch, and then just roc build.roc will do the rest. Just remember to reference the platform using a local relative path instead of a URL.

view this post on Zulip Aurélien Geron (Jul 30 2024 at 08:04):

I just tested, it works fine indeed, thanks again. Note, I did not have to compile from source, I just copied the definition of sequence from the PR into my file and used that instead of Task.sequence. In case anyone else wants to go that route:

sequence = \taskList ->
    Task.loop (taskList, List.withCapacity (List.len taskList)) \(tasks, values) ->
        when tasks is
            [task, .. as rest] ->
                value = task!
                Task.ok (Step (rest, List.append values value))

            [] ->
                Task.ok (Done values)

view this post on Zulip Luke Boswell (Jul 30 2024 at 08:05):

Oh nice. Was that modifying the version in your cache?

view this post on Zulip Aurélien Geron (Jul 30 2024 at 08:31):

I'm sorry, I'm not sure I understand your question. What is the cache you are referring to? I only edited main.roc and added the sequence def there. I had to revert to basic-cli 0.12 because I was getting this error with 0.13:

roc_app_binary(18551,0x7ff844aa6fc0) malloc: *** error for object 0x7fb44c905b78: pointer being freed was not allocated
roc_app_binary(18551,0x7ff844aa6fc0) malloc: *** set a breakpoint in malloc_error_break to debug

view this post on Zulip Luke Boswell (Jul 30 2024 at 08:41):

Oh ok. That also works too.

I was talking about editing the platform implementation in the downloaded files. When you use a URL package roc downloads and put the .roc files into your .cache folder. So, you might also be able to patch it there. I think the check against files hashing correctly is only at the time when it's first downloaded and put in .cache


Last updated: Jul 06 2025 at 12:14 UTC