当前位置: 首页 产品详细

editplus怎样编译c语言

一、请问可不可以在EditPlus上编写C语言程序啊?

可以,但编译要自己做:

菜单文本:编译

命令:C:\ProgramFiles(x86)\Dev-Cpp\MinGW64\bin\g++.exe(g++在电脑里的位置)

参数:$(FilePath)-o$(FileNameNoExt)-mwindows

二、editplus++如何编译c/c++文件

这个只是文本文件编辑工具,只用于编写源代码,不能编译。

C++编译工具要另外下载安装,常见的有MS

C+、Rust编译一样糟糕?我用1.7万行代码试了试

作者|quick-lint-js

译者|弯月责编|屠敏

出品|CSDN(ID:CSDNnews)

众所周知,C++的编译十分缓慢。编程圈子有一个著名的梗:“代码正在编译”,这个梗就来自C++。

像GoogleChromium这样的项目在最新的硬件上也需要一个小时才能构建完成,在旧硬件上则需要长达六个小时。文档里记载了数不清的加速编译的技巧,还有许多很容易出错的捷径,用来减少每次编译的代码量。即使使用数千美元的云计算,Chromium的构建时间也需要几十分钟。我完全无法接受这一点。这样如何能正常工作?

Rust也有类似的传言:编译时间是个大问题。但这真的是Rust的问题,还是黑Rust的谣言?跟C++的编译时间相比,Rust又如何呢?

我很关心编译速度和运行时性能。更快的构建测试循环可以提高生产力,而且可以让编程更快乐,那么我就能让软件的运行速度更快,客户也能更开心。所以,我决定亲眼看看Rust是否真的像他们说的那么差。我的计划如下:

找一个开源C++项目;

将项目的某一部分单独分离出来变成一个小项目;

用Rust逐行重写C++代码;

优化C++项目和Rust项目的编译时间;

比较两个项目的编译和测试时间。

我的假设是(猜测,不是结论):

Rust的代码行数比C++略少。

因为C++中大多数函数和方法都需要定义两次(头文件中一次,实现中一次)。Rust则不需要这样做,因此代码行数就会减少。

需要进行完整编译时,C++比Rust需要更多时间(即Rust胜出)。

这是因为C++的#include和模板需要在每个.cpp中进行编译。虽然可以并行进行,但并行并不完美。

对于增量构建,Rust的编译时间比C++多(即C++胜出)。

这是因为Rust一次编译一个crate,而不像C++那样一次编译一个文件,所以即使只有很小的变化,Rust也要重新编译更多的代码。

你认为如何?我进行了一项调查:

42%的人认为C++会获胜,35%的人认为需要具体分析,17%的人认为Rust会获胜。

下面,实验开始!

寻找C++和Rust的实验对象

寻找项目

如果需要花一个月来移植代码,我要移植哪个呢?我的挑选条件如下:

很少或没有第三方依赖(标准库没关系);

可以在Linux或macOS上运行(我不太关心在Windows上的编译时间);

有大量的测试用例(没有测试用例,我没办法知道我写的Rust代码是否正确);

涉及多种技术:FFI、指针、标准和自定义容器、工具类和函数、I/O、并发、泛型、宏、SIMD、继承。

最后的选择很简单:选我前几年写过的项目!我将移植之前在quick-lint-js(https://quick-lint-js.com/blog/cpp-vs-rust-build-times/#:~:text=quick%2Dlint%2Djs%20project)项目中编写的JavaScript词法分析器。

修剪C++代码

quick-link-js的C++部分包含大约10万行代码。我不会把这么多代码全都移植到Rust,否则要花费一年时间!所以只选了JavaScript词法分析器部分。这需要涉及项目中的其他部分:

诊断系统

翻译系统(用于诊断)

多种内存分配器和容器(如bump分配器、适用于SIMD的字符串等)

多种工具函数(如UTF-8解码器、SIMD封装等)

测试辅助代码(如自定义的断言宏)

CAPI

不幸的是,这个子集并不包含任何并发或I/O代码。也就是说,我没办法测试Rust的async/await在编译时间上的额外开销。不过在quick-lint-js中这种代码并不多,所以不是什么大问题。

首先,我复制了所有C++代码,然后删掉了与词法分析器无关的东西,如语法分析器和LSP服务器等,直到无法删除任何代码为止。整个过程中都要保证C++测试通过。

将quick-lint-js的代码精简到词法分析器(以及它所需的任何其他代码)之后,得到了大约1.7万行C++代码:

重写

如何重写数千行C++代码呢?只能一次重写一个文件。下面是具体的过程:

从某个模块着手;

复制代码和测试,用查找替换的方法修正某些语法,然后不断运行cargotest,直到编译通过、测试通过;

如果需要先转换其他模块,则返回第二步对其进行转换,然后再回到该模块;

如果还有模块尚未转换,则返回第一步。

Rust和C++项目有一个主要区别可能会影响编译时间。在C++项目中,诊断系统中包含许多代码生成、宏和constexpr。而在Rust移植中,我采用了代码生成、proc宏、普通的宏,还有一些const。我听说proc宏很慢的原因只是它们很难写好。我希望我的proc宏写得还不错。

最后的Rust项目要比C++项目略大一些。C++有16,600行代码,而Rust有17,100行。

优化Rust的编译时间

我很在意编译时间。因此,我的C++项目已经针对编译时间做了许多优化。我需要针对Rust项目进行类似的优化。

我们来尝试一下以下手段,以优化Rust项目的编译时间:

更快的连接器

Cranelift后端

编译器和连接器的标志

不同的工作区和测试布局

尽可能减少依赖特性

cargo-nextest

通过PGO定制的工具链

更快的连接器

第一步是对构建进行性能测试。首先通过-Zself-profile标志进行测试。在我的项目中,该标志会输出两个不同的文件。在其中一个文件中,run_linker阶段的时间最长:

我曾经将连接器换成moldlinker,成功地改善了C++的编译时间。我们在Rust项目上试试看:

很可惜,几乎看不到显著的改善。

上面是Linux的情况。macOS也有另一个连接器:lld和zld。我们试试看:

在macOS上,换成另一种连接器也没有任何显著的改善。可能是因为Linux和macOS的默认连接器对于我的小项目来说已经非常优秀了。进一步优化的连接器(Mold、lld、zld)可能在大型项目上表现更好。

Cranelift后端

我们再来看看-Zself-profile性能测试。对于另一个文件来说,LLVM_module_-codegen_emit_obj和LLVM_passes阶段时间最长:

我听说,除了默认的rustc后端LLVM之外,还有一个名为Cranelift的后端。我用rustcCranelift后端尝试编译了一下,-Zself-profile的结果很令人振奋:

但很可惜,使用Cranelift的实际编译时间甚至还不如LLVM:

编译器和连接器选项

编译器有许多开关,可以加速编译(或减缓编译)。我们来尝试一部分:

-Zshare-generics=y(rustc)(实验性质的选项)

-Clink-args=-Wl,-s(rustc)

debug=false(Cargo)

debug-assertions=false(Cargo)

incremental=trueandincremental=false(Cargo)

overflow-checks=false(Cargo)

panic='abort'(Cargo)

lib.doctest=false(Cargo)

lib.test=false(Cargo)

注意:quick,-Zshare-generics=y相当于quick,incremental=true加上启用-Zshare-generics=y标志。其他条形图没有启用-Zshare-generics=y,因为该选项仍不稳定(因此只能用仍在开发中的Rust编译器)。

大部分选项都有文档,但我没看到有人说过使用-s连接选项。-s能删除调试信息,包括静态连接的Rust标准库中的调试信息。这就意味着连接器的工作量更少,从而能减少连接的时间。

工作区和测试布局

Rust和Cargo对于文件的位置有一定的灵活性。该项目有三种合理的布局:

理论上,如果将代码分割到多个crate中,Cargo就能并行调用rustc。由于我的Linux机器有一个32线程的CPU,macOS机器有一个10线程CPU,所以感觉启用并行应该能降低构建时间。

editplus怎样编译c语言

对于给定的crate,Rust项目中也有多个地方可以放置测试用例:

由于依赖循环,我没办法针对tests位于src内的布局进行测试。但我针对其他布局的各种组合进行了测试:

工作区配置(不论是分离的测试可执行文件(即多个测试用的exe文件)或合并成一个测试可执行文件(只有一个测试用的exe))似乎效果最好。所以我们后文采用工作区、多个测试可执行文件的配置。

尽可能减少依赖特性

许多crate支持可选的特性。有时,可选特性是默认启用的。我们用cargotree看看启用了哪些特性:

libccrate有一个特性名为std。我们将其禁用并测试,看看构建时间是否有改善:

构建时间并没有任何提高。也许std特性并没有什么有意义的工作?

cargo-nextest

cargo-nextest工具宣称“相较于cargotest,速度最多可以提高60%”。我的Rust代码中有44%都是测试,也许cargo-nextest有用。我们来试试并比较一下构建和测试的时间。

在我的Linux机器上,cargo-nextest并没有改善,也没有变差。虽然输出结果漂亮了许多……

在macOS上会怎样呢?

在我的MacBookPro上,cargo-nextest的确快了那么一点点。不知道为什么加速跟操作系统有关。也许实际上跟硬件有关?

采用通过PGO定制的工具链

对于C++构建来说,我发现通过PGO(profile-guidedoptimizations,根据性能测试进行的优化,有时也称FDO)编译出的C++编译器,在性能上有很大提升。我们针对Rust工具链尝试一下PGO,然后再尝试用LLVMBOLT优化rustc,以及-Ctarget-cpu=native。

与C++编译器相比,似乎通过rustup发布的Rust工具链已经优化得很好了。PGO+BOLT带来的性能提升不到10%。但提升就是提升,所以接下来我们使用优化后的工具链与C++作比较。

优化C++构建

在原始的C++项目quick-lint-js上工作时,我已经使用常见的技术对其进行了优化,如PCH、禁用异常和RTTI、调整构建选项、删除无用的#include、将代码移出头文件、将模板实例化外置等。但C++有多种编译器和连接器。我们来比较一下它们,然后选择最好的一个跟Rust进行比较。

在Linux上,GCC显然是个异类。Clang要快得多。而我自己构建的Clang(与Rust构建一样,采用了PGO和BOLT)比Ubuntu自带的Clang又有很大提升。libstdc++构建平均而言比libc++快一点点。我们采用自己构建的Clang和libstd++,代表C++与Rust进行比较。

在macOS上,Xcode自带的Clang似乎比LLVM网站上提供的Clang工具链更好。我采用Xcode的Clang与Rust比较。

C++20模块

我的C++代码使用了#include。但C++20的import怎样呢?C++20的模块会让编译更快吗?

我在项目中尝试了C++20。截至目前,Linux上的CMake对于模块的支持仍然处于早期试验阶段,就连基本的helloworld都不能正常工作。

也许2023年C++20的模块会有长足发展。我非常希望如此,但至少目前,我只能用C++传统的#include。

C++和Rust的构建时间比较

我把C++项目移植到了Rust,并尽可能优化了Rust的构建时间。现在哪个编译器更快,C++还是Rust?

在我的Linux机器上,Rust构建有时候比C++快,但有时慢,或者不相上下。在incrementallex测试中(该测试修改的文件最大),Clang比rustc更快。但对于其他增量测试,rustc领先。

但是,在macOS上,结论完全不同。C++构建通常比Rust构建快得多。在incrementaltest-utf-8测试中(该测试修改的文件为中等大小),rustc编译得比Clang略快。但在所有其他增量测试以及完整构建中,Clang显然要快得多。

对于超过1.7万行的大项目

我只测试了1.7万行代码的小项目。对于大项目(比如10万行),构建时间如何呢?

为了测试C++和Rust编译器在大项目上的表现,我选择了最大的模块(词法分析器)并将其代码和测试用例复制了多个副本(8个、16个以及24个)。

由于我的性能测试也包括了运行测试的时间,所以我认为时间应该会线性增加。

Rust和Clang的编译时间都是线性增长的,符合我的预期。

对于C++而言,头文件(incrementaldiag-types)的变化对构建时间的影响最大,这一点符合预期。在其他增量测试中,构建时间增长的幅度较小,这要归功于Mold连接器。

我对Rust感到失望,即使在incrementaltest-utf-8测试中,rust的表现也不尽如人意(该测试添加了一些不相关的文件,因此不应该会受到太大影响)。该测试使用了工作区、多个测试exe文件,这意味着test-utf-8应该有自己的可执行文件,应该是单独编译的。

结论

Rust的编译时间是问题吗?是。有许多技巧可以加快构建,但我没找到任何方法能够带来数量级上的提升。

Rust的构建时间是否和C++一样差?是。对于大型项目,开发的编译时间甚至比C++更差,至少对于我的编程风格是这样。

回顾一下我的假设,可以看到假设的所有方面都错了:

Rust移植版本比C++版本的代码行数更多,而不是更少。

对于完整编译(1.7万行代码),C++消耗的时间基本上与Rust相同,甚至更少(10万+代码),而不会更多。

对于增量构建,Rust有时候消耗时间更短,有时更长(1.7万行代码),或者长得多(10万+代码),但也不一定。

是不是很失望?是的。在移植的过程中我学到了许多Rust的知识。例如,proc宏可以替换三种不同的代码生成器,从而简化构建流水线,也可以让贡献者更容易提交代码。我并不想念头文件,我也很感谢Rust的工具(特别是Cargo、rustup和miri)。

我决定不再移植quick-lint-js的其余部分到Rust。但如果构建时间能显著改善,也许我会改变主意。

发布人:qianming96 发布时间:2024-07-26