Use iterator for lazy evaluation in journal storage’s read_logs#6144
Use iterator for lazy evaluation in journal storage’s read_logs#6144c-bata merged 10 commits intooptuna:masterfrom
read_logs#6144Conversation
|
This pull request has not seen any recent activity. |
|
This pull request was closed automatically because it had not seen any recent activity. If you want to discuss it, you can reopen it freely. |
|
@sawa3030 Could you review this PR? |
|
Though I was initially concerned about runtime, the difference seems to be small.
I generated the plot using the following scripts, adapted from the code suggested here: read_logs.pyrun_benchmark.shvisualize.py |
|
@kAIto47802 The initial impression of this PR looks very good to me. Could you merge the latest master branch to resolve CI issues? |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #6144 +/- ##
==========================================
- Coverage 89.21% 89.13% -0.08%
==========================================
Files 209 209
Lines 13935 13935
==========================================
- Hits 12432 12421 -11
- Misses 1503 1514 +11 ☔ View full report in Codecov by Sentry. |
|
Thank you for the comment. I've merged the latest master branch, resolving the conflict. |
c-bata
left a comment
There was a problem hiding this comment.
I confirmed that the memory usage decreased from 632MB to 229MB while loading 100k trials (the journal file contained 500,001 lines). LGTM!
master
$ scalene --memory journal_storage_mem.py
Memory usage: ▁▁▁▁▁▂▂▃▃▃▃▃▃▄▄▅▅▅▅▅▆▇▇████ (max: 632.315 MB, growth rate: 98%)
% of time = 100.00% (20.507s) out of 20.507s.
:
This PR
$ scalene --memory journal_storage_mem.py
Memory usage: ▁▁▂▂▂▃▃▃▄▄▅▅▅▆▆▇▆▇▇▇███ (max: 229.319 MB, growth rate: 96%)
% of time = 100.00% (19.675s) out of 19.675s.
:
`journal_storage_mem.py`
- Create a Python snippet.
import optuna
import time
from optuna.storages import JournalStorage
from optuna.storages.journal import JournalFileBackend
study_name = "journal_storage_bench"
journal_file_path = "./journal_storage_bench.log"
def objective(trial: optuna.Trial) -> float:
x = trial.suggest_float("x", -10, 10)
y = trial.suggest_float("y", -10, 10)
trial.set_user_attr("dummy_attr", "dummy_value")
return (x - 2) ** 2 + (y - 3) ** 2
def create_study() -> None:
storage = JournalStorage(JournalFileBackend(journal_file_path))
sampler = optuna.samplers.RandomSampler(1)
study = optuna.create_study(storage=storage, sampler=sampler, direction="minimize", study_name=study_name, load_if_exists=True)
start = time.time()
study.optimize(objective, n_trials=100000, n_jobs=10)
elapsed = time.time() - start
print(f"Elapsed time: {elapsed:.4f} seconds")
if __name__ == "__main__":
# create_study()
JournalStorage(JournalFileBackend(journal_file_path))This snippet

Motivation
Currently, the
read_logsfunction in journal storage returns a list, meaning all log entries are loaded into memory at once. This leads to high memory usage, especially when handling a large number of entries.To address this issue, this PR introduces lazy evaluation by replacing the list with a generator, allowing entries to be processed one by one without loading everything into memory.
Description of the changes
Benchmarking
I conduct a benchmark on memory usage to confirm the effectiveness of this PR.
Benchmarking Setup
I create a
JournalStorageinstance with large log files and profile the memory usage right after it invokesapply_logswith the result ofread_logsin the_sync_with_backendmethod.The code I use to create the large log files is as follows:
The code to create the large log files
The code I use to profile the memory usage after
apply_logsfor the master branch and the branch of this PR is as follows, respectively:The code I use to run the benchmark and visualize the results is as follows:
The benchmarking code
read_logs.py
run_benchmark.sh
The visualization code
Result
The result is shown in Figure 1, confirming that this PR effectively reduces the memory usage.
Figure 1. The memory usage after the invocation of
apply_logswith large log files. The solid lines denote the mean, and the shaded regions denote the standard error, both computed over five independent runs with different random seeds. Compared to the master branch (shown in pink), this PR (shown in blue) effectively reduces the memory usage.