Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fable: Fix excessive compilation duration #206

Merged
merged 7 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions fable/examples/stress/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)

project(fable_stress_test LANGUAGES CXX)

set(LARGE_STRUCT_SIZE 1000 CACHE NUMBER "Number of members of Large struct")

find_package(CLI11 REQUIRED)
find_package(fable REQUIRED)
find_package(fmt REQUIRED)

# Executable ---------------------------------------------------------
add_custom_command(
OUTPUT large_struct.hxx
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen.py ${LARGE_STRUCT_SIZE} ${CMAKE_CURRENT_BINARY_DIR}/large_struct.hxx
VERBATIM
)
add_executable(stress
src/main.cpp
large_struct.hxx
)
target_include_directories(stress
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
set_target_properties(stress PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
target_link_libraries(stress
PRIVATE
fable::fable
fmt::fmt
CLI11::CLI11
)
31 changes: 31 additions & 0 deletions fable/examples/stress/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This is a minimal Makefile to show how this example project
# can be built with Conan and CMake.

# All generated and compiled output is in this directory.
BUILD_DIR := build

# The build type must be synchronized between Conan (host build type)
# and CMake, otherwise you will get strange and unhelpful errors.
BUILD_TYPE := Release
CMAKE_BUILD_TYPE := $(shell echo ${BUILD_TYPE} | tr '[:upper:]' '[:lower:]')

# This is the output that Conan generates when using
# `CMakeToolchain` generator AND the `cmake_layout` layout.
TOOLCHAIN_FILE := ${BUILD_DIR}/${BUILD_TYPE}/generators/conan_toolchain.cmake

# How many variables should the "Large" struct have?
LARGE_STRUCT_SIZE := 1000

FABLE_VERSION := $(shell make --quiet -C ../.. info-fqn)

.PHONY: all clean
all: ${TOOLCHAIN_FILE}
cmake --build --preset=${CMAKE_BUILD_TYPE}

${TOOLCHAIN_FILE}:
conan install . --build=missing --install-folder=${BUILD_DIR} -s:h build_type=${BUILD_TYPE} --require-override ${FABLE_VERSION}
cmake --preset=${CMAKE_BUILD_TYPE} -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DLARGE_STRUCT_SIZE=${LARGE_STRUCT_SIZE}

clean:
-rm -r ${BUILD_DIR}
-rm CMakeUserPresets.json
2 changes: 2 additions & 0 deletions fable/examples/stress/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This example project stresses the compiler by forcing it to compile
an extremely large struct.
15 changes: 15 additions & 0 deletions fable/examples/stress/conanfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This is a minimal conanfile.txt to show how this example project
# can be built with Conan and CMake.

[requires]
cli11/2.3.2
fable/[>=0.20.0, include_prerelease=True]@cloe/develop
fmt/9.1.0
boost/[>=1.65.1]

[generators]
CMakeDeps
CMakeToolchain

[layout]
cmake_layout
104 changes: 104 additions & 0 deletions fable/examples/stress/gen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3

import random
import string
import argparse

SIMPLE_TYPES = [
"bool",
"uint8_t",
"uint16_t",
"uint32_t",
"uint64_t",
"int8_t",
"int16_t",
"int32_t",
"int64_t",
"std::string",
]

COMPLEX_TYPES = [
"std::vector",
"std::map",
"boost::optional",
# "std::array", # not supported yet
]

ALL_TYPES = SIMPLE_TYPES + COMPLEX_TYPES

def generate_random_type():
def _tvector():
inner = random.choice(SIMPLE_TYPES)
return f"std::vector<{inner}>"

def _tmap():
inner = random.choice(SIMPLE_TYPES)
return f"std::map<std::string, {inner}>"

def _toptional():
inner = random.choice(SIMPLE_TYPES)
return f"boost::optional<{inner}>"

def _tarray():
inner = random.choice(SIMPLE_TYPES)
length = random.randint(2, 24)
return f"std::array<{inner}, {length}>"

mapper = {
"std::vector": _tvector,
"std::map": _tmap,
"boost::optional": _toptional,
}

result = random.choice(ALL_TYPES)
if result in COMPLEX_TYPES:
return mapper[result]()
return result

def generate_random_word():
letters = string.ascii_letters
return ''.join(random.choice(letters) for _ in range(20))

TEMPLATE = string.Template("""
#pragma once

struct Large : public fable::Confable {
$variable_lines

CONFABLE_SCHEMA(Large) {
using namespace fable;
return Schema{
$schema_lines
};
}
};
""")

def generate(count) -> str:
variable_lines = []
schema_lines = []
for i in range(count):
random_word = generate_random_word()
variable_lines.append(f"{generate_random_type()} v{i};")
schema_lines.append('{{"{0}", make_schema(&v{1}, "")}},'.format(random_word, i))
result = TEMPLATE.substitute({
"variable_lines": "\n ".join(variable_lines),
"schema_lines": "\n ".join(schema_lines),
})
return result

def main():
parser = argparse.ArgumentParser()
parser.add_argument("count", default=10, type=int)
parser.add_argument("output", default="-", type=str)
args = parser.parse_args()

result = generate(args.count)
if args.output == "-":
print(result)
else:
with open(args.output, "w") as file:
file.write(result)

if __name__ == "__main__":
main()
48 changes: 48 additions & 0 deletions fable/examples/stress/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2021 Robert Bosch GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* \file fable/examples/stress/src/main.cpp
*
* In this example application, we will stress-test the compilation.
* We will be re-using types from the contacts example.
*/

#include <iostream> // for std::{cout, cerr}
#include <string> // for std::string<>
#include <vector> // for std::vector<>

#include <fmt/format.h> // for fmt::format
#include <CLI/CLI.hpp> // for CLI::App
#include <boost/optional.hpp> // for boost::optional<>

#include <fable/confable.hpp> // for fable::{Confable, CONFABLE_SCHEMA}
#include <fable/schema.hpp> // for fable::{Schema, String}
#include <fable/utility.hpp> // for fable::{read_conf}

#include "large_struct.hxx"

int main(int argc, char** argv) {
// Parse command line arguments:
CLI::App app("Fable Stress Test Example");
std::string filename;
CLI11_PARSE(app, argc, argv);

Large large;
std::cout << large.schema().to_json().dump(2) << std::endl;
}
8 changes: 4 additions & 4 deletions fable/include/fable/enum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,22 +149,22 @@ struct EnumSerializerImpl<T, false> {
}
};

template <typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
const std::map<T, std::string>& enum_serialization() {
return EnumSerializerImpl<T, has_enum_serializer<T>::value>::serialization_impl();
}

template <typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
const std::map<std::string, T>& enum_deserialization() {
return EnumSerializerImpl<T, has_enum_serializer<T>::value>::deserialization_impl();
}

template <typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
std::string to_string(T x) {
return enum_serialization<T>().at(x);
}

template <typename T, std::enable_if_t<std::is_enum<T>::value, int> = 0>
template <typename T, std::enable_if_t<std::is_enum_v<T>, int> = 0>
T from_string(const std::string& s) {
return enum_deserialization<T>().at(s);
}
Expand Down
24 changes: 10 additions & 14 deletions fable/include/fable/schema.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,12 @@

#pragma once

#include <chrono> // for duration<>
#include <map> // for map<>
#include <memory> // for shared_ptr<>
#include <string> // for string
#include <type_traits> // for enable_if_t<>, is_arithmetic<>, is_enum<>, ...
#include <utility> // for move
#include <vector> // for vector<>

#include <boost/optional.hpp> // for optional<>

#include <fable/schema/array.hpp> // for Array<>
#include <fable/schema/boolean.hpp> // for Boolean
#include <fable/schema/confable.hpp> // for FromConfable
Expand Down Expand Up @@ -181,28 +177,28 @@ class Schema : public schema::Interface {
Schema& operator=(const Schema&) = default;

// Struct
Schema(std::string&& desc, schema::PropertyList<> props)
Schema(std::string desc, schema::PropertyList<> props)
: impl_(new schema::Struct(std::move(desc), props)) {}

Schema(schema::PropertyList<> props) : Schema("", props) {}

Schema(std::string&& desc, const Schema& base, schema::PropertyList<> props)
Schema(std::string desc, const Schema& base, schema::PropertyList<> props)
: impl_(new schema::Struct(std::move(desc), base, props)) {}

Schema(const Schema& base, schema::PropertyList<> props) : Schema("", base, props) {}

// Variant
Schema(const std::vector<Schema>& xs); // NOLINT(runtime/explicit)
Schema(std::string&& desc, const std::vector<Schema>& xs);
Schema(std::string desc, const std::vector<Schema>& xs);

Schema(schema::BoxList props); // NOLINT(runtime/explicit)
Schema(std::string&& desc, schema::BoxList props);
Schema(std::string desc, schema::BoxList props);

Schema(schema::BoxVec&& props); // NOLINT(runtime/explicit)
Schema(std::string&& desc, schema::BoxVec&& props);
Schema(std::string desc, schema::BoxVec&& props);

// Interface
template <typename T, std::enable_if_t<std::is_base_of<schema::Interface, T>::value, int> = 0>
template <typename T, std::enable_if_t<std::is_base_of_v<schema::Interface, T>, int> = 0>
Schema(const T& value) : impl_(value.clone()) {} // NOLINT(runtime/explicit)
Schema(schema::Interface* i) : impl_(i) { assert(impl_); } // NOLINT(runtime/explicit)
Schema(std::shared_ptr<schema::Interface> i) : impl_(std::move(i)) { // NOLINT(runtime/explicit)
Expand All @@ -211,19 +207,19 @@ class Schema : public schema::Interface {

// Ignore
Schema() : impl_(new schema::Ignore("")) {}
explicit Schema(std::string&& desc, JsonType t = JsonType::object)
explicit Schema(std::string desc, JsonType t = JsonType::object)
: impl_(new schema::Ignore(std::move(desc), t)) {}

// Primitives
template <typename T>
Schema(T* ptr, std::string&& desc) : impl_(make_schema(ptr, std::move(desc)).clone()) {}
Schema(T* ptr, std::string desc) : impl_(make_schema(ptr, std::move(desc)).clone()) {}
template <typename T>
Schema(T* ptr, const schema::Box& prototype, std::string&& desc)
Schema(T* ptr, const schema::Box& prototype, std::string desc)
: impl_(make_schema(ptr, prototype, std::move(desc)).clone()) {}

// FromJson
template <typename T>
Schema(T* ptr, JsonType t, std::string&& desc)
Schema(T* ptr, JsonType t, std::string desc)
: impl_(new schema::FromJson<T>(ptr, t, std::move(desc))) {}

public: // Special
Expand Down
20 changes: 10 additions & 10 deletions fable/include/fable/schema/array.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ template <typename T, typename P>
class Array : public Base<Array<T, P>> {
public: // Types and Constructors
using Type = std::vector<T>;
using PrototypeSchema = P;
using PrototypeSchema = std::remove_cv_t<std::remove_reference_t<P>>;

Array(Type* ptr, std::string&& desc);
Array(Type* ptr, const PrototypeSchema& prototype)
: Base<Array<T, P>>(JsonType::array), prototype_(prototype), ptr_(ptr) {}
Array(Type* ptr, const PrototypeSchema& prototype, std::string&& desc)
: Base<Array<T, P>>(JsonType::array, std::move(desc)), prototype_(prototype), ptr_(ptr) {}
Array(Type* ptr, std::string desc);
Array(Type* ptr, PrototypeSchema prototype)
: Base<Array<T, P>>(JsonType::array), prototype_(std::move(prototype)), ptr_(ptr) {}
Array(Type* ptr, PrototypeSchema prototype, std::string desc)
: Base<Array<T, P>>(JsonType::array, std::move(desc)), prototype_(std::move(prototype)), ptr_(ptr) {}

#if 0
// This is defined in: fable/schema/magic.hpp
Array(Type* ptr, std::string&& desc)
Array(Type* ptr, std::string desc)
: Array(ptr, make_prototype<T>(), std::move(desc)) {}
#endif

Expand Down Expand Up @@ -173,9 +173,9 @@ class Array : public Base<Array<T, P>> {
Type* ptr_{nullptr};
};

template <typename T, typename P>
Array<T, P> make_schema(std::vector<T>* ptr, const P& prototype, std::string&& desc) {
return Array<T, P>(ptr, prototype, std::move(desc));
template <typename T, typename P, typename S>
Array<T, P> make_schema(std::vector<T>* ptr, P&& prototype, S&& desc) {
return Array<T, P>(ptr, std::forward<P>(prototype), std::forward<S>(desc));
}

} // namespace schema
Expand Down
5 changes: 3 additions & 2 deletions fable/include/fable/schema/boolean.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Boolean : public Base<Boolean> {
public: // Types and Constructors
using Type = bool;

Boolean(Type* ptr, std::string&& desc);
Boolean(Type* ptr, std::string desc);

public: // Overrides
Json json_schema() const override;
Expand All @@ -51,7 +51,8 @@ class Boolean : public Base<Boolean> {
Type* ptr_{nullptr};
};

inline Boolean make_schema(bool* ptr, std::string&& desc) { return Boolean(ptr, std::move(desc)); }
template <typename S>
inline Boolean make_schema(bool* ptr, S&& desc) { return Boolean(ptr, std::forward<S>(desc)); }

} // namespace schema
} // namespace fable
Loading
Loading