Skip to content

dace.Config is not Thread Safe and a Globally Shared Object #2125

@philip-paul-mueller

Description

@philip-paul-mueller

I am pretty sure that all of us are aware that dace.Config is not thread safe.
However, I think that this aspect has been overlooked until now and its impact was not realized, as we all thought, that this does not happen since we will not use DaCe in a multi threaded context.
Today I realized that this is not true as we run the tests in parallel which means that DaCe is affected by this issues:

Consider the following function:

def my_awesome_optimizer(sdfg, optimizer_options) -> dace.CompiledSDFG:
    with dace.config.temporary_config():
        configure_dace_for_my_awesome_optimizer()
        return _my_awesome_optimizer_impl(sdfg, optimizer_options)

Now consider two thread and the following scenario:

  • Thread 1 enters my_awesome_optimizer() which will create a new "configuration context".
    This means it will safe the old configuration and store it inside a file, for the sake of argument, let's assume that this are the default settings.
  • Thread 1 will then enter the implementation of the optimizer and start doing stuff, which will take some time.
  • Thread 2 will also enter my_awesome_optimizer() and also create a new configuration context.
    However, it will store the configuration that was modified by thread 1 to disc.
  • Thread 2 will then also enter the implementation of the optimizer.
  • Thread 1 has finished the optimization process and exits the with clause, this will trigger the restoration of the stored configuration.
    This means that now the default settings have been restored and have been activated.
  • From this point on thread 2 will no longer use the configuration that was established by `configure_dace_for_my_awesome_optimizer(), instead it will use the default configuration.

Thus it is quite clear that there are issues and that they are present inside the tests.

Another implication from the fact that dace.Config is a globally shared object, is that it actually limits what you can set.
Consider the following:

def _compile_with_different_opt_level(sdfg, opt_level):
    with dace.config.temporary_config():
        dace.Config.set("optimization_level", value=opt_level)
        return sdfg.compile()

@pytest.mar.parameterize("opt_level_and_timing", [("-O2", 1.0), ("-O3", 0.5)])
def test_compile_opti(opt_level_and_timing):
    opt_level, timing = opt_level_and_timing
    sdfg = get_sdfg()
    csdfg = _compile_with_different_opt_level(sdfg, opt_level)
    mean_runtime = timeit.time(csdfg)
    assert mean_runtime < timing

With a similar scenario it is kind of arbitrary which compiler flags are used.

The solution would be to turn the configuration object into thread local data.

The bug is present in current main: a214529

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions