5

I have a function of type

f: fn(x: SomeType, y: Arc<()>) -> ISupposeTheReturnTypeDoesNotMatter

when compiled (with or without optimization), will the y be optimized away?

The intention of the y is to limit the number of the running instances of f, if y is referenced too many times, the caller of f will not call f until the reference count of y becomes lower.

Edit: clarification on my intention

The intention is to keep the number of running http requests (represented by the above f) in control, the pseudo code looks like this:

let y = Arc::new(());
let all_jobs_to_be_done = vector_of_many_jobs;
loop {
    while y.strong_count() < some_predefined_limit {
        // we have some free slots, fill them up with instances of f,
        // f is passed with a clone of y,
        // so that the running of f would increase the ref count,
        // and the death of the worker thread would decrease the ref count
        let work = all_jobs_to_be_done.pop();
        let ticket = y.clone();
        spawn_work(move || {f(work, ticket)});
    }

    sleep_for_a_few_seconds();
}

The reason for this seemingly hacky work around is that I cannot find a library that meets my needs (consume a changing work queue with bounded amount of async (tokio) workers, and requeue the work if the job fails)

7
  • Sounds like an interesting question, write a small test to investigate how it behaves Commented Sep 2, 2020 at 1:20
  • 4
    I prefer specification over observation. For debug build, the observation is that it won't be optimized away, but this could also mean that I didn't meet the requirement of the optimizer, perhaps for some other code arrangement, it will be optimized away. Commented Sep 2, 2020 at 1:23
  • The debug build will not optimize anything away unless you manually specify a higher level of optimization. Commented Sep 2, 2020 at 1:50
  • 1
    With all the refcount-limitation logic in the caller, why pass the Arc to the callee? As you say, it’s unused there... I don’t understand the purpose of keeping it in the function’s signature. Commented Sep 2, 2020 at 2:55
  • @eggyal To take advantage of the move semantics and variable lifetime, see my edits. Commented Sep 2, 2020 at 12:07

1 Answer 1

6

Will Rust optimize away unused function arguments?

Yes, LLVM (the backend for rustc) is able to optimize away unused variables when removing them does not change program behavior, although nothing guarantees it will do it. rustc has some passes before LLVM too, but the same applies.

Knowing what exactly counts as program behavior is tricky business. However, multi-threaded primitives used in refcounting mechanics are usually the sort of thing that cannot be optimized away for good reason. Refer to the Rust reference for more information (other resources that might help are the nomicon, the different GitHub repos, the Rust fora, the C++11 memory model which Rust uses, etc.).

On the other hand, if you are asking about what are the semantics of the language when it encounters unused parameters, then no, Rust does not ignore them (and hopefully never will!).

will the y be optimized away?

No, it is a type with side effects. For instance, dropping it requires running non-trivial code.

The intention of the y is to limit the number of the running instances of f

Such an arrangement does not limit how many threads are running f since Arc is not a mutex and, even if it were some kind of mutex, you could construct as many independent ones as you wanted.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for the clarification. The limitation is done manually, one the caller of f, there is something like this if y.strong_count() > some_number then wait_until_ref_count_lowers() else f(x, y.clone()), The limitation is not a hard limit, as long as it around a certain number, it's ok.
You're welcome! If the semaphore logic is outside, then there is no need to pass it to f.
@Acorn FWIW while Arc does have side-effects, the function could be inlined and arc-cloning in the parent removed entirely. If you remove the -O you can see the call to Arc::clone in the compiled output, but with -O the entire thing goes away.
@Masklinn The call to a function named clone is removed, but Arc's cloning (as a concept) is not removed, the lock add and lock sub instructions are present in the optimized output. In the context of the OP's question the argument was not optimized away, for reasons explained in this answer.
@Masklinn It is not removed, you still have the branching and the locked ops as user4815162342 mentions. Those are the sort of things I was referring to in the answer with "multi-threaded primitives used in refcounting mechanics". What you are seeing as "removed" is just the consequence of inlining and other passes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.