Open retep998 opened 7 years ago
This issue has been opened because the building times with rust-qt are quite high. Here's a simple example:
Specs
cpu: Intel(R) Core(TM) i7-4720HQ CPU @ 2.60 Ghz, cores=4, threads=8
rustc 1.12.1 (d4f39402a 2016-10-19)
binary: rustc
commit-hash: d4f39402a0c2c2b94ec0375cd7f7f6d7918113cd
commit-date: 2016-10-19
host: x86_64-pc-windows-msvc
release: 1.12.1
Cargo.toml
[dependencies]
qt_core = "*"
qt_gui = "*"
qt_widgets = "*"
Debug mode
cargo rustc -- -Z time-passes > log_debug.txt 2>&1
The backlog is:
warning: the option `Z` is unstable and should only be used on the nightly compiler, but it is currently accepted for backwards compatibility; this will soon change, see issue #31847 for more details
time: 0.000; rss: 15MB parsing
time: 0.000; rss: 16MB configuration
time: 0.000; rss: 16MB recursion limit
time: 0.000; rss: 16MB crate injection
time: 0.000; rss: 16MB plugin loading
time: 0.000; rss: 16MB plugin registration
time: 0.034; rss: 48MB expansion
time: 0.000; rss: 48MB maybe building test harness
time: 0.000; rss: 49MB assigning node ids
time: 0.000; rss: 49MB checking for inline asm in case the target doesn't support it
time: 0.000; rss: 49MB complete gated feature checking
time: 0.000; rss: 49MB collecting defs
time: 0.003; rss: 49MB external crate/lib resolution
time: 0.000; rss: 49MB early lint checks
time: 0.000; rss: 49MB AST validation
time: 0.000; rss: 49MB name resolution
time: 0.000; rss: 50MB lowering ast -> hir
time: 0.000; rss: 50MB indexing hir
time: 0.000; rss: 50MB attribute checking
time: 0.000; rss: 50MB language item collection
time: 0.000; rss: 50MB lifetime resolution
time: 0.000; rss: 50MB looking for entry point
time: 0.000; rss: 50MB looking for plugin registrar
time: 0.000; rss: 50MB region resolution
time: 0.000; rss: 50MB loop checking
time: 0.000; rss: 50MB static item recursion checking
time: 0.000; rss: 50MB load_dep_graph
time: 0.000; rss: 50MB type collecting
time: 0.000; rss: 50MB variance inference
time: 0.006; rss: 51MB coherence checking
time: 0.000; rss: 52MB wf checking
time: 0.000; rss: 52MB item-types checking
time: 0.000; rss: 53MB item-bodies checking
time: 0.000; rss: 53MB drop-impl checking
time: 0.000; rss: 53MB const checking
time: 0.000; rss: 53MB privacy checking
time: 0.000; rss: 53MB stability index
time: 0.000; rss: 53MB intrinsic checking
time: 0.000; rss: 53MB effect checking
time: 0.000; rss: 53MB match checking
time: 0.000; rss: 53MB liveness checking
time: 0.000; rss: 53MB rvalue checking
time: 0.000; rss: 54MB MIR dump
time: 0.001; rss: 54MB MIR passes
time: 0.000; rss: 54MB borrow checking
time: 0.000; rss: 54MB reachability checking
time: 0.000; rss: 54MB death checking
time: 0.000; rss: 54MB stability checking
time: 0.000; rss: 54MB unused lib feature checking
time: 0.000; rss: 54MB lint checking
time: 0.002; rss: 54MB resolving dependency formats
time: 0.000; rss: 54MB Prepare MIR codegen passes
time: 0.000; rss: 55MB write metadata
time: 0.001; rss: 55MB translation item collection
time: 0.000; rss: 55MB codegen unit partitioning
time: 0.000; rss: 58MB internalize symbols
time: 0.011; rss: 58MB translation
time: 0.000; rss: 58MB assert dep graph
time: 0.000; rss: 58MB serialize dep graph
time: 0.000; rss: 58MB llvm function passes [0]
time: 0.000; rss: 58MB llvm module passes [0]
time: 0.002; rss: 60MB codegen passes [0]
time: 0.000; rss: 60MB codegen passes [0]
time: 0.004; rss: 60MB LLVM passes
time: 0.000; rss: 60MB serialize work products
time: 0.149; rss: 60MB running linker
time: 0.151; rss: 60MB linking
Finished debug [unoptimized + debuginfo] target(s) in 4202.78 secs
Release mode
cargo rustc --release -- -Z time-passes > log_release.txt 2>&1
The backlog is:
warning: the option `Z` is unstable and should only be used on the nightly compiler, but it is currently accepted for backwards compatibility; this will soon change, see issue #31847 for more details
time: 0.000; rss: 15MB parsing
time: 0.000; rss: 16MB configuration
time: 0.000; rss: 16MB recursion limit
time: 0.000; rss: 16MB crate injection
time: 0.000; rss: 16MB plugin loading
time: 0.000; rss: 16MB plugin registration
time: 0.039; rss: 48MB expansion
time: 0.000; rss: 48MB maybe building test harness
time: 0.000; rss: 49MB assigning node ids
time: 0.000; rss: 49MB checking for inline asm in case the target doesn't support it
time: 0.000; rss: 49MB complete gated feature checking
time: 0.000; rss: 49MB collecting defs
time: 0.003; rss: 49MB external crate/lib resolution
time: 0.000; rss: 49MB early lint checks
time: 0.000; rss: 49MB AST validation
time: 0.000; rss: 49MB name resolution
time: 0.000; rss: 50MB lowering ast -> hir
time: 0.000; rss: 50MB indexing hir
time: 0.000; rss: 50MB attribute checking
time: 0.000; rss: 50MB language item collection
time: 0.000; rss: 50MB lifetime resolution
time: 0.000; rss: 50MB looking for entry point
time: 0.000; rss: 50MB looking for plugin registrar
time: 0.000; rss: 50MB region resolution
time: 0.000; rss: 50MB loop checking
time: 0.000; rss: 50MB static item recursion checking
time: 0.000; rss: 50MB load_dep_graph
time: 0.000; rss: 50MB type collecting
time: 0.000; rss: 50MB variance inference
time: 0.006; rss: 51MB coherence checking
time: 0.000; rss: 52MB wf checking
time: 0.000; rss: 52MB item-types checking
time: 0.001; rss: 53MB item-bodies checking
time: 0.000; rss: 53MB drop-impl checking
time: 0.000; rss: 53MB const checking
time: 0.000; rss: 53MB privacy checking
time: 0.000; rss: 53MB stability index
time: 0.000; rss: 53MB intrinsic checking
time: 0.000; rss: 53MB effect checking
time: 0.000; rss: 53MB match checking
time: 0.000; rss: 53MB liveness checking
time: 0.000; rss: 53MB rvalue checking
time: 0.000; rss: 54MB MIR dump
time: 0.001; rss: 54MB MIR passes
time: 0.000; rss: 54MB borrow checking
time: 0.000; rss: 54MB reachability checking
time: 0.000; rss: 54MB death checking
time: 0.000; rss: 54MB stability checking
time: 0.000; rss: 54MB unused lib feature checking
time: 0.000; rss: 54MB lint checking
time: 0.002; rss: 54MB resolving dependency formats
time: 0.000; rss: 54MB Prepare MIR codegen passes
time: 0.000; rss: 55MB write metadata
time: 0.001; rss: 55MB translation item collection
time: 0.000; rss: 55MB codegen unit partitioning
time: 0.000; rss: 57MB internalize symbols
time: 0.011; rss: 57MB translation
time: 0.000; rss: 57MB assert dep graph
time: 0.000; rss: 57MB serialize dep graph
time: 0.001; rss: 58MB llvm function passes [0]
time: 0.002; rss: 59MB llvm module passes [0]
time: 0.003; rss: 62MB codegen passes [0]
time: 0.001; rss: 62MB codegen passes [0]
time: 0.009; rss: 62MB LLVM passes
time: 0.000; rss: 62MB serialize work products
time: 0.161; rss: 62MB running linker
time: 0.162; rss: 62MB linking
Finished release [optimized] target(s) in 1865.86 secs
But the main problem is not the crazy times when doing a cold build on the qt crates but the crazy times it takes to build the simplest qt example, for instance, let's consider this dummy example:
extern crate qt_widgets;
use qt_widgets::application::Application;
use qt_widgets::push_button::PushButton;
use qt_widgets::cpp_utils::*;
use qt_widgets::qt_core::string::String;
use qt_widgets::libc::{c_char, c_int};
fn to_qt_string<S: AsRef<str>>(s: S) -> String {
let slice = s.as_ref().as_bytes();
String::from_utf8((slice.as_ptr() as *const c_char, slice.len() as c_int, AsStruct))
}
fn from_qt_string(string: &String) -> std::string::String {
let buf = string.to_utf8(AsStruct);
unsafe {
let bytes = std::slice::from_raw_parts(buf.const_data() as *const u8, buf.count(()) as usize);
std::str::from_utf8_unchecked(bytes).to_string()
}
}
fn main() {
let _app = Application::new((&mut 0i32, &mut (&mut 0i8 as *mut i8) as *mut *mut i8, AsBox));
let mut btn = PushButton::new((&to_qt_string("first_button"), AsBox));
let mut btn2 = PushButton::new((&to_qt_string("second_button"), AsBox));
let text = from_qt_string(&btn.text(AsStruct));
let text2 = from_qt_string(&btn2.text(AsStruct));
btn.show();
btn2.show();
let ret = Application::exec();
}
If i build and then touch
this file (no editing at all, just saving/changing timestamp) and I run cargo rustc -- -Z time-passes
the backlog will be:
Compiling qt_workspace v0.1.0 (file:///D:/sources/personal/rust/qt_workspace)
warning: the option `Z` is unstable and should only be used on the nightly compiler, but it is currently accepted for backwards compatibility; this will soon change, see issue #31847 for more details
time: 0.001; rss: 16MB parsing
time: 0.000; rss: 16MB configuration
time: 0.000; rss: 16MB recursion limit
time: 0.000; rss: 16MB crate injection
time: 0.000; rss: 16MB plugin loading
time: 0.000; rss: 16MB plugin registration
time: 0.034; rss: 48MB expansion
time: 0.000; rss: 48MB maybe building test harness
time: 0.000; rss: 48MB assigning node ids
time: 0.000; rss: 48MB checking for inline asm in case the target doesn't support it
time: 0.000; rss: 48MB complete gated feature checking
time: 0.000; rss: 48MB collecting defs
time: 0.234; rss: 217MB external crate/lib resolution
time: 0.000; rss: 217MB early lint checks
time: 0.000; rss: 217MB AST validation
time: 0.002; rss: 219MB name resolution
time: 0.000; rss: 220MB lowering ast -> hir
time: 0.000; rss: 220MB indexing hir
time: 0.000; rss: 220MB attribute checking
time: 0.000; rss: 220MB language item collection
time: 0.000; rss: 220MB lifetime resolution
time: 0.000; rss: 220MB looking for entry point
time: 0.000; rss: 220MB looking for plugin registrar
time: 0.000; rss: 220MB region resolution
time: 0.000; rss: 220MB loop checking
time: 0.000; rss: 220MB static item recursion checking
time: 0.000; rss: 220MB load_dep_graph
time: 0.001; rss: 221MB type collecting
time: 0.000; rss: 221MB variance inference
time: 0.020; rss: 224MB coherence checking
time: 0.002; rss: 225MB wf checking
time: 0.000; rss: 225MB item-types checking
time: 0.025; rss: 229MB item-bodies checking
time: 0.000; rss: 229MB drop-impl checking
time: 0.001; rss: 229MB const checking
time: 0.000; rss: 229MB privacy checking
time: 0.000; rss: 229MB stability index
time: 0.000; rss: 229MB intrinsic checking
time: 0.000; rss: 229MB effect checking
time: 0.000; rss: 229MB match checking
time: 0.000; rss: 229MB liveness checking
time: 0.000; rss: 229MB rvalue checking
time: 0.001; rss: 230MB MIR dump
time: 0.000; rss: 230MB MIR passes
time: 0.000; rss: 231MB borrow checking
time: 0.000; rss: 231MB reachability checking
time: 0.000; rss: 231MB death checking
time: 0.000; rss: 231MB stability checking
time: 0.000; rss: 231MB unused lib feature checking
warning: unused variable: `text`, #[warn(unused_variables)] on by default
--> main.rs:26:7
|
26 | let text = from_qt_string(&btn.text(AsStruct));
| ^^^^
warning: unused variable: `text2`, #[warn(unused_variables)] on by default
--> main.rs:27:7
|
27 | let text2 = from_qt_string(&btn2.text(AsStruct));
| ^^^^^
warning: unused variable: `ret`, #[warn(unused_variables)] on by default
--> main.rs:30:7
|
30 | let ret = Application::exec();
| ^^^
time: 0.006; rss: 231MB lint checking
time: 0.002; rss: 231MB resolving dependency formats
time: 0.001; rss: 231MB Prepare MIR codegen passes
time: 0.000; rss: 231MB write metadata
time: 0.026; rss: 239MB translation item collection
time: 0.000; rss: 239MB codegen unit partitioning
time: 0.001; rss: 244MB internalize symbols
time: 0.164; rss: 244MB translation
time: 0.000; rss: 244MB assert dep graph
time: 0.000; rss: 244MB serialize dep graph
time: 0.001; rss: 239MB llvm function passes [0]
time: 0.000; rss: 239MB llvm module passes [0]
time: 0.018; rss: 242MB codegen passes [0]
time: 0.000; rss: 242MB codegen passes [0]
time: 0.021; rss: 242MB LLVM passes
time: 0.000; rss: 242MB serialize work products
time: 34.444; rss: 242MB running linker
time: 34.446; rss: 242MB linking
Finished debug [unoptimized + debuginfo] target(s) in 35.5 secs
If i do the same process for release > cargo rustc --release -- -Z time-passes
the backlog will be:
Compiling qt_workspace v0.1.0 (file:///D:/sources/personal/rust/qt_workspace)
warning: the option `Z` is unstable and should only be used on the nightly compiler, but it is currently accepted for backwards compatibility; this will soon change, see issue #31847 for more details
time: 0.000; rss: 16MB parsing
time: 0.000; rss: 16MB configuration
time: 0.000; rss: 16MB recursion limit
time: 0.000; rss: 16MB crate injection
time: 0.000; rss: 16MB plugin loading
time: 0.000; rss: 16MB plugin registration
time: 0.040; rss: 48MB expansion
time: 0.000; rss: 48MB maybe building test harness
time: 0.000; rss: 48MB assigning node ids
time: 0.000; rss: 48MB checking for inline asm in case the target doesn't support it
time: 0.000; rss: 48MB complete gated feature checking
time: 0.000; rss: 48MB collecting defs
time: 0.241; rss: 217MB external crate/lib resolution
time: 0.000; rss: 217MB early lint checks
time: 0.000; rss: 217MB AST validation
time: 0.002; rss: 219MB name resolution
time: 0.000; rss: 219MB lowering ast -> hir
time: 0.000; rss: 220MB indexing hir
time: 0.000; rss: 220MB attribute checking
time: 0.000; rss: 220MB language item collection
time: 0.000; rss: 220MB lifetime resolution
time: 0.000; rss: 220MB looking for entry point
time: 0.000; rss: 220MB looking for plugin registrar
time: 0.000; rss: 220MB region resolution
time: 0.000; rss: 220MB loop checking
time: 0.000; rss: 220MB static item recursion checking
time: 0.000; rss: 220MB load_dep_graph
time: 0.001; rss: 220MB type collecting
time: 0.000; rss: 221MB variance inference
time: 0.021; rss: 224MB coherence checking
time: 0.002; rss: 224MB wf checking
time: 0.000; rss: 224MB item-types checking
time: 0.026; rss: 229MB item-bodies checking
time: 0.000; rss: 229MB drop-impl checking
time: 0.000; rss: 229MB const checking
time: 0.000; rss: 229MB privacy checking
time: 0.000; rss: 229MB stability index
time: 0.000; rss: 229MB intrinsic checking
time: 0.000; rss: 229MB effect checking
time: 0.000; rss: 229MB match checking
time: 0.000; rss: 229MB liveness checking
time: 0.000; rss: 229MB rvalue checking
time: 0.001; rss: 230MB MIR dump
time: 0.000; rss: 230MB MIR passes
time: 0.000; rss: 231MB borrow checking
time: 0.000; rss: 231MB reachability checking
time: 0.000; rss: 231MB death checking
time: 0.000; rss: 231MB stability checking
time: 0.000; rss: 231MB unused lib feature checking
warning: unused variable: `text`, #[warn(unused_variables)] on by default
--> main.rs:26:7
|
26 | let text = from_qt_string(&btn.text(AsStruct));
| ^^^^
warning: unused variable: `text2`, #[warn(unused_variables)] on by default
--> main.rs:27:7
|
27 | let text2 = from_qt_string(&btn2.text(AsStruct));
| ^^^^^
warning: unused variable: `ret`, #[warn(unused_variables)] on by default
--> main.rs:30:7
|
30 | let ret = Application::exec();
| ^^^
time: 0.006; rss: 231MB lint checking
time: 0.003; rss: 231MB resolving dependency formats
time: 0.001; rss: 231MB Prepare MIR codegen passes
time: 0.000; rss: 231MB write metadata
time: 0.026; rss: 239MB translation item collection
time: 0.000; rss: 239MB codegen unit partitioning
time: 0.001; rss: 245MB internalize symbols
time: 0.160; rss: 245MB translation
time: 0.000; rss: 245MB assert dep graph
time: 0.000; rss: 245MB serialize dep graph
time: 0.002; rss: 239MB llvm function passes [0]
time: 0.019; rss: 241MB llvm module passes [0]
time: 0.010; rss: 243MB codegen passes [0]
time: 0.001; rss: 243MB codegen passes [0]
time: 0.034; rss: 243MB LLVM passes
time: 0.000; rss: 243MB serialize work products
time: 35.683; rss: 244MB running linker
time: 35.685; rss: 244MB linking
Finished release [optimized] target(s) in 36.32 secs
And that's the main problem, you can't iterate with these crazy times for a trivial example. Hopefully all this info will help to improve rust. Amazing package manager, cool language syntax and features... but... if building times are that crazy I don't see which benefits would bring me to stay away from c++/qt or python/pyqt.
Regards.
I always envisioned that we would use any incremental linking functionality available when also compiling incrementally. That is certainly a good idea. Always using it also for regular builds is probably not a good idea because of binary sizes.
Show we expose incremental linking as a separate -C
flag? Cargo.toml
could have an incremental
profile key that could be set to link-only
?
The Gold linker also allows for incremental linking.
cc @rust-lang/tools @rust-lang/compiler
Seems plausible to me, I'd be fine just defaulting to using this with -C incremental=foo
once that's stabilized.
hey @alexcrichton Whats the update on this? Servo takes an awful long time to compile on Windows and i think its related to the lack of this. Is there anything i can do to push this forward?
Not much has happened on this AFAIK, but we can still implement it at any time!
FWIW, here is the log of one of my normal Rust projects that's not using Qt or any huge lib, also after just touch
ing a file.
Win 8.1, rustc 1.26.0-nightly (2789b067d 2018-03-06)
The build before that one (also after just touch
ing a src file) took 42 seconds.
This is a lot, especially considering that cargo check
in sublime and cargo watch -x run
can't run in parallel, so when I save a file in Sublime, they always have to run both, and the order is random, whoever gets the lock first. And 80% of the time, cargo watch -x run
gets the lock first and then I will only get the inline errors in sublime after both have finished..
So, please at least allow running check
and build
/run
in parallel, not exclusively!
So, please at least allow running check and build/run in parallel, not exclusively!
That sounds unrelated to this issue, consider opening a new one (in the cargo repository).
@Boscop, are these timings with incremental compilation enabled?
Don't delete the output artifacts. The .ilk and .exe or .dll must remain from the previous run.
@retep998 @alexcrichton are these all generated in the target folder?
@Jayflux I'm not sure, but the incremental folder can be assumed to be preserved and so intermediate artifacts can be stored somewhere in there.
Don't pass /OPT to the linker or any other flag that is mutually exclusive with incremental linking.
Unless ive got this wrong, this bit of work has already been done
The incremental folder stores object files, but before linking they are copied to where the compiler would otherwise generate them. The final executable/dll is not touched by incremental compilation. I don't know if we have an explicit step in the compiler that would delete it before invoking the linker.
@michaelwoerister Incremental is enabled by default, right? So then it must be enabled.
@Boscop It's only the default for debug builds and cargo check
. The output above says Finished dev [optimized + debuginfo] target(s)
, so I'm wondering. On the other hand, it shows a very high number of object files being generated, which usually indicates incremental compilation. If you run cargo
with --verbose
, then we'd know for sure.
If this is with incremental compilation enabled then this is some kind of bug or at least a severe corner case that we should investigate. Just touch
ing a file is usually the best case scenario and no object files should have to be recompiled.
I have this in my Cargo.toml:
[profile.dev]
incremental = true
opt-level = 1
Because otherwise it was too slow (for real-time audio) in debug mode and using too much cpu..
Is there a way I can keep the opt-level but make the compilation faster?
@Boscop This should not be re-compiling so much with incremental compilation after just touching a file. Could you open an issue about this, if possible with a way to reproduce?
I don't know of a way to make this faster. As long as incremental compilation doesn't work for your project, you should turn it off and use codegen-units = 4
(or however many CPU cores you have) instead.
What is there to do in this issue? Are the OP instructions still valid?
The long compilation times are still a problem
On Servo my build is much faster after i set my local cargo to use lld-link.exe from LLVM. Downloaded from https://llvm.org/builds/ then
[target.x86_64-pc-windows-msvc]
linker = "lld-link.exe"
@jasonwilliams Did you download the win32 or win64 version?
@Boscop i used the win32 version, any particular reason you ask? Looking again at the LLVM website i see there is a 64bit version, i may try that
@Boscop This should not be re-compiling so much with incremental compilation after just touching a file. Could you open an issue about this, if possible with a way to reproduce?
@michaelwoerister how can i reproduce this isssue?
Any plans to still consider supporting this? Linking is still a pain point on incremental builds (often takes almost all the time), & it would be great if we could take advantage of this on windows. Trying to proxy project deps through a dylib is fairly cumbersome, as it quickly falls into you needing to pass -Cprefer-dynamic & then patch deps to other dylib proxies.
I tried passing -Clink-args=/INCREMENTAL, but I'm not seeing any .ilk files in the build directory. (-Clink-args=/DEBUG:NONE does however help quite a bit by disabling the .pbds)
Is lld-link.exe fast enough? My understanding is that modern (almost) fully parallel linkers like lld are often just as fast as incremental linking with old school (mostly) non-parallel linkers like link.exe.
lld definitely seems faster than link. Though maybe the gap between them has been brought down [2019, 2021]? Here's some very rough numbers using a pretty contrived example that pulls in a bunch of deps. In my project I'm building a dylib that I then hot reload in my main program, so I'm seeing now that linking the dylib (along with -Zshare-generics=n -Cprefer-dynamic), appears to increase link times quite a bit vs a fully static lib:
Machine: CPU=AMD 7700X, RAM=32GB 4400Mhz DDR5, SSD=RefS-formatted Samsung 990 Pro With lib as dylib, imported by main (for hot-reloading): linker |
full | /DEBUG:NONE |
---|---|---|
link.exe | 6.70 | 4.03 |
rust-lld | 5.05 | 2.74 |
With static lib: linker | full |
---|---|
link.exe | 4.0 |
rust-lld | 3.78 |
Machine: CPU=i7-7700HQ, RAM=16GB 2400MHz DDR4, SSD=Samsung MZVLV256HCHP With lib as dylib, imported by main (for hot-reloading): linker | full | /DEBUG:NONE |
---|---|---|
link.exe | 15.34 | 10.31 |
rust-lld | 12.74 | 7 |
I'm not sure how parallel lld is, eyeballing the task manager it only looked like 3 threads were getting used (vs what looked like 2 for link?), admittedly not very scientific on my end 😆. Curious on how mold
would perform here.
It's a good point that it warrants seeing if link.exe + /INCREMENTAL is actually faster than lld. Looking online, I'm not really even seeing much info concerning link.exe full vs incremental other than https://devtalk.blender.org/t/speed-up-c-compilation/30508/14
Disk | Full Hot | Full Cold | Incremental Hot | Incremental Cold |
---|---|---|---|---|
HDD | 27.375 | 218.187 | 7.688 | 164.324 |
SDD | 24.381 | 56.774 | 7.627 | 11.887 |
If we also see a 3x improvement then that would be pretty great for incremental builds. On the other hand, it looks like incremental linking can also run into the problems reported in https://github.com/godotengine/godot/issues/77968
Incremental linking will allow for significantly faster link times, at the cost of binary size.
.ilk
and.exe
or.dll
must remain from the previous run./OPT
to the linker or any other flag that is mutually exclusive with incremental linking./INCREMENTAL
to the linker.https://msdn.microsoft.com/en-us/library/4khtbfyf.aspx
Alternatively, provide LLD as a stable alternative to link.exe so users can benefit from faster links when they don't need any special features of link.exe.