Skip to content

Commit

Permalink
Precompile headers at runtime instead
Browse files Browse the repository at this point in the history
  • Loading branch information
thecodingwizard committed Jul 9, 2024
1 parent bb1eeee commit b61fe68
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 17 deletions.
11 changes: 0 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@ RUN dnf install -y gcc-c++
# For -fsanitize=undefined and -fsanitize=address
RUN dnf install -y libasan libubsan

# Precompile bits/stdc++.h: https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
# I believe flags like -Wall are ignored, but flags like -std, -O2, and -fsanitize=address must
# match the flags used to precompile the header.
RUN mkdir -p /precompiled-headers/bits/stdc++.h.gch
RUN g++ -std=c++11 -O2 -o /precompiled-headers/bits/stdc++.h.gch/01 /usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h
RUN g++ -std=c++17 -O2 -o /precompiled-headers/bits/stdc++.h.gch/02 /usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h
RUN g++ -std=c++23 -O2 -o /precompiled-headers/bits/stdc++.h.gch/03 /usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h
RUN g++ -std=c++11 -O2 -fsanitize=address -o /precompiled-headers/bits/stdc++.h.gch/04 /usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h
RUN g++ -std=c++17 -O2 -fsanitize=address -o /precompiled-headers/bits/stdc++.h.gch/05 /usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h
RUN g++ -std=c++23 -O2 -fsanitize=address -o /precompiled-headers/bits/stdc++.h.gch/06 /usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h

RUN dnf install -y java-21-amazon-corretto-devel
RUN dnf install -y time

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,16 @@ Future updates: Maybe https://awscli.amazonaws.com/v2/documentation/api/latest/r

Todo:
- precompile `bits/stdc++.h`

---


```js
for (let i = 0; i < 100; i++) fetch("https://v3nuswv3poqzw6giv37wmrt6su0krxvt.lambda-url.us-east-1.on.aws/compile", {
method: "POST",
headers: {"Content-Type": "application/json" }, body: JSON.stringify({
"source_code": "cat /proc/cpuinfo && sleep 1",
"compiler_options": "-O2 -std=c++17",
"language": "cpp"
}) }).then(x => x.json()).then(x => console.log(x.compile_output.stdout.match(/cpu MHz\t\t: (.*)/)[1]))
```
77 changes: 72 additions & 5 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ use std::{
fs::{self, File},
io::Write,
os::unix::process::ExitStatusExt,
process::ExitStatus,
path::Path,
process::{Command, ExitStatus},
};

use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use axum::Json;
use base64::{prelude::BASE64_STANDARD, Engine};
use serde::{Deserialize, Serialize};
use tempdir::TempDir;

use crate::{
error::AppError, run_command::{run_command, CommandOptions, CommandOutput}, types::{Executable, Language}
error::AppError,
run_command::{run_command, CommandOptions, CommandOutput},
types::{Executable, Language},
};

#[derive(Deserialize)]
Expand All @@ -31,6 +34,66 @@ pub struct CompileResponse {
pub compile_output: CommandOutput,
}

/// Precompile bits/stdc++.h if necessary.
///
/// Building bits/stdc++.h can be very slow. We can substantially speed this up by precompiling
/// headers: https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
///
/// However, precompiling headers is slow (~6s for C++23), and /tmp storage space is expensive, so
/// we only precompile bits/stdc++.h for some compiler options.
///
/// I believe flags like -Wall are ignored, but flags like -std, -O2, and -fsanitize=address must
/// match the flags used to precompile the header.
///
/// We don't do this precompilation in the dockerfile because lambda disk read speeds are abysmally
/// slow (~6 MB/s empirically), and the precompiled headers are quite large.
fn check_precompile_headers(compile_request: &CompileRequest) -> Result<()> {
let compiler_options = &compile_request.compiler_options;

if compile_request.language != Language::Cpp
|| !compile_request.source_code.contains("bits/stdc++.h")
|| !compiler_options.contains("-O2")
{
return Ok(());
}

const PRECOMPILE_VERSIONS: &'static [&'static str] = &["11", "17", "23"];

let mut cpp_version = "";
for version in PRECOMPILE_VERSIONS {
if compiler_options.contains(&format!("-std=c++{version}")) {
cpp_version = version;
}
}
if cpp_version == "none" {
return Ok(());
}

let precompiled_header_path =
format!("/tmp/precompiled-headers/bits/stdc++.h.gch/{cpp_version}");

if Path::new(&precompiled_header_path).exists() {
return Ok(());
}

if !Command::new("g++")
.arg("-o")
.arg(precompiled_header_path)
.arg(format!("-std=c++{cpp_version}"))
.arg("-O2")
.arg("/usr/include/c++/11/x86_64-amazon-linux/bits/stdc++.h")
.status()
.with_context(|| format!("Failed to precompile header"))?
.success()
{
return Err(anyhow!(
"Command to precompile header exited with nonzero exit code"
));
}

Ok(())
}

pub fn compile(compile_request: CompileRequest) -> Result<CompileResponse> {
let tmp_dir = TempDir::new("compile")?;
let tmp_out_dir = TempDir::new("compile-out")?;
Expand All @@ -45,16 +108,20 @@ pub fn compile(compile_request: CompileRequest) -> Result<CompileResponse> {
.into_string()
.map_err(|_| anyhow!("failed to convert output_file_path into string"))?;

if let Err(err) = check_precompile_headers(&compile_request) {
println!("Warning: Failed to precompile headers: {err}");
}

let command = format!(
"g++ -I/precompiled-headers -o {} {} program.cpp",
"g++ -I/tmp/precompiled-headers -o {} {} program.cpp",
output_file_path, compile_request.compiler_options
);
let compile_output = run_command(
&command,
tmp_dir.path(),
CommandOptions {
stdin: String::new(),
timeout_ms: 5000,
timeout_ms: 600000,
},
)?;

Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fs;

use axum::{
routing::{get, post}, Json, Router
};
Expand All @@ -22,6 +24,8 @@ async fn index_page() -> &'static str {
async fn main() -> Result<(), Error> {
tracing::init_default_subscriber();

fs::create_dir_all("/tmp/precompiled-headers/bits/stdc++.h.gch")?;

let app = Router::new()
.route("/", get(index_page))
.route("/compile", post(compile_handler))
Expand Down
2 changes: 1 addition & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, PartialEq)]
pub enum Language {
#[serde(rename = "cpp")]
Cpp,
Expand Down

0 comments on commit b61fe68

Please sign in to comment.