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

[Bug]: Extensive memory usage of gmock with Clang compilers [1.12.0 regression] #4156

Closed
trzecieu opened this issue Feb 17, 2023 · 3 comments
Assignees

Comments

@trzecieu
Copy link
Contributor

trzecieu commented Feb 17, 2023

Describe the issue

Hello,

We've noticed that gmock 1.12.0+ started to require much more system memory and time to compile. This issue affects only clang compilers:

  • Clang 12 (from Emscripten)
  • Clang 17(from Emscripten)
  • AppleClang 14 (Xcode 14)
    Possibly some versions in between. Standard 86-64 Linux clang shows a similar tendency, but memory consumption is smaller.

I do understand this is a bug on the edge of code/compiler bug. But still I'd like to highlight this as a potential problem that might affect silently everyone that uses google tests.

Some graphs with memory consumption over time:


Emscripten 2.0.0 (clang version 12.0.0) + googletest 1.10.0
image

Emscripten 2.0.0 (clang version 12.0.0) + googletest 1.12.1
image


Emscripten 3.1.31 (clang version 17.0.0) + googletest 1.10.0
image

Emscripten 3.1.31 (clang version 17.0.0) + googletest 1.12.1
image


Ubuntu clang version 14.0.0-1ubuntu1 + googletest 1.10.0
image

Ubuntu clang version 14.0.0-1ubuntu1 + googletest 1.12.1
image


g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 + googletest 1.10.0
image

g++ (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 + googletest 1.12.1
image


Consistently 1.12.1 consumes much more memory. With this synthetic example, it is approaching 350MiB, but in a production code, these values are exceeding hardware capabilities. Here are graphs from 1 compilation unit from our production code:

1.10.0 (~1GiB, ~25s to compile)
image

1.12.1 (~3GiB, ~75s to compile)
image

Steps to reproduce the problem

This issue is especially visible if one compilation unit contains a several semi-complicated mock classes.

The synthetic test can be done with:

#include "gmock/gmock.h"

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winconsistent-missing-override"
#endif

class IAb{};
class IA{};
class IB{};
class IBb{};
class ID{};
class IDb{};
class IEb{};
class IE{};
class IC{};
class ICb{};
class IF{};
class IFb{};
class IG{};
class IGb{};
class IJ{};
class IH{};
class IHb{};
class IK{};
class IL{};
class IM{};
class IN{};
class IO{};
class IP {};

class IPPP {
public:
	virtual ~IPPP() = default;
	virtual IA* f1(IAb*) = 0;
	virtual void f2(IA*) = 0;
	virtual IB* f3(IBb*) = 0;
	virtual void f4(IB*) = 0;
	virtual ID* f5(IDb*) = 0;
	virtual void f6(ID*) = 0;
	virtual IE* f7(IEb*) = 0;
	virtual void f8(IE*) = 0;
	virtual IC* f9(ICb*) = 0;
	virtual void f10(IC*) = 0;
	virtual IF* f11(IFb*) = 0;
	virtual void f12(IF*) = 0;
	virtual IG* f13(IGb*) = 0;
	virtual void f14(IG*) = 0;
	virtual IJ* f15() = 0;
	virtual IH* f16() = 0;
	virtual IHb* f16b() = 0;
	virtual IK* f17() const = 0;
	virtual IL* f18() = 0;
	virtual std::string f19() = 0;
	virtual IM* f19() const = 0;
	virtual IN* f20() const = 0;
	virtual void f21(IO* IO, const IP& IP) = 0;
	virtual IH& f22() = 0;
	virtual void f23() = 0;
};

class MockSystem : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};


class MockSystem1 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem1() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem1() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};


class MockSystem2 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem2() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem2() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};



class MockSystem3 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem3() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem3() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};


class MockSystem4 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem4() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem4() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};

class MockSystem5 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem5() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem5() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};
class MockSystem6 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem6() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem6() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};
class MockSystem7 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem7() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem7() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};
class MockSystem8 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem8() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem8() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};
class MockSystem9 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem9() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem9() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};
class MockSystem10 : public IPPP {
public:
	MOCK_METHOD1(f1, IA*(IAb*));
	MOCK_METHOD1(f2, void(IA*));
	MOCK_METHOD1(f3, IB*(IBb*));
	MOCK_METHOD1(f4, void(IB*));
	MOCK_METHOD1(f5, ID*(IDb*));
	MOCK_METHOD1(f6, void(ID*));
	MOCK_METHOD1(f7, IE*(IEb*));
	MOCK_METHOD1(f8, void(IE*));
	MOCK_METHOD1(f9, IC*(ICb*));
	MOCK_METHOD1(f10, void(IC*));
	MOCK_METHOD1(f11, IF*(IFb*));
	MOCK_METHOD1(f12, void(IF*));
	MOCK_METHOD1(f13, IG*(IGb*));
	MOCK_METHOD1(f14, void(IG*));
	MOCK_METHOD0(f15, IJ*());
	MOCK_METHOD0(f16, ::IH*());
	MOCK_METHOD0(f16b, IHb*());
	MOCK_CONST_METHOD0(f17, IK*());
	MOCK_METHOD0(f18, IL*());
	MOCK_METHOD0(f19, std::string());
	MOCK_CONST_METHOD0(f19, IM*());
	MOCK_CONST_METHOD0(f20, IN*());
	MOCK_METHOD2(f21, void(IO* IO, const IP& IP));
	MOCK_METHOD0(f22, IH&());
	MOCK_METHOD0(f23, void());

	MockSystem10() {
		testing::DefaultValue<IM*>::Set(nullptr);
		testing::DefaultValue<IK*>::Set(nullptr);
		testing::DefaultValue<IN*>::Set(nullptr);
	}

	~MockSystem10() override {
		testing::DefaultValue<IM*>::Clear();
		testing::DefaultValue<IK*>::Clear();
		testing::DefaultValue<IN*>::Clear();
	}
};

#ifdef __clang__
#pragma clang diagnostic pop
#endif

What version of GoogleTest are you using?

Last good version: 1.11.0
First broken version: 1.12.0

To be precise, this issue was introduced/exaggerated with this commit 9d21db9
CC: @jacobsa

What operating system and version are you using?

cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"

But this also is visible on macOS Monterey/Ventura with Xcode 13/14

What compiler and version are you using?

Blocker:

/home/piotr/SDK/emsdk/upstream/bin/clang++ --version
clang version 12.0.0 (/b/s/w/ir/cache/git/chromium.googlesource.com-external-git.luolix.top-llvm-llvm--project a3036b386383f1c1e9d32c2c8dba995087959da3)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/piotr/SDK/emsdk/upstream/bin
  • Xcode 13/14 - looks version independent

Major:

clang++ --version
Ubuntu clang version 14.0.0-1ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

What build system are you using?

N/A

Additional context

No response

@trzecieu trzecieu changed the title [Bug]: Extensive memory usage on Clang (especially clang 12) [Bug]: Extensive memory usage of gmock with Clang compilers Feb 17, 2023
@trzecieu trzecieu changed the title [Bug]: Extensive memory usage of gmock with Clang compilers [Bug]: Extensive memory usage of gmock with Clang compilers [1.12.0 regression] Feb 20, 2023
@higher-performance
Copy link
Collaborator

Just a shot in the dark from a quick glance at the commit, but I wonder if the change from "untyped" to "typed" (UntypedInvokeWith/UntypedPerformDefaultAction to InvokeWith/PerformDefaultAction) may have something to do with this.

Also note that if you can run Clang with -ftime-trace before and after the commit, comparing them might help narrow down which new instantiations are happening that didn't exist before. (The JSON file it produces can be loaded & viewed in chrome://tracing in the browser.)

@trzecieu
Copy link
Contributor Author

trzecieu commented Apr 6, 2023

Thanks @higher-performance for the suggestion with -ftime-trace. Here are results:

Optimal (either version 1.11.0, or version with reverted commit 9d21db9)
image

Suboptimal (Version 1.12.0+)
image

As you can see there is an extraordinary amount of overhead coming from PerformPendingInstantiations and cascades to InstantiateFunction.

Those are traced from: FunctionMocker, like

testing::internal::FunctionMocker<foo::optional<Bar> ()>::Invoke

and cascade to:

std::__2::forward_as_tuple<(lambda at /home/piotr/Projects/meta/thirdparty/gmock/1.12.1/include/gmock/gmock-spec-builders.h:1814:33)>

std::__2::__tuple_constructible<std::__2::tuple<const (lambda at /home/piotr/Projects/meta/thirdparty/gmock/1.12.1/include/gmock/gmock-spec-builders.h:1814:33) &>, std::__2::__tuple_types<const (lambda at /home/piotr/Projects/meta/thirdparty/gmock/1.12.1/include/gmock/gmock-spec-builders.h:1814:33) &>, true, true>

std::__2::__tuple_impl<std::__2::__tuple_indices<0>, const std::__2::allocator<(lambda at /home/piotr/Projects/meta/thirdparty/gmock/1.12.1/include/gmock/gmock-spec-builders.h:1770:9)> &>

Which in my case is:

// ...
    const Cleanup report_uninteresting_call(
        [&] { ReportUninterestingCall(reaction, ss.str()); }); // <===== 1770
// ...
  const Cleanup handle_failures([&] {  // <===== 1814
    ss << "\n" << why.str();

    if (!found) {
      // No expectation matches this call - reports a failure.
      Expect(false, nullptr, -1, ss.str());
    } else if (is_excessive) {
      // We had an upper-bound violation and the failure message is in ss.
      Expect(false, untyped_expectation->file(), untyped_expectation->line(),
             ss.str());
    } else {
      // We had an expected call and the matching expectation is
      // described in ss.
      Log(kInfo, loc.str() + ss.str(), 2);
    }
  });

@higher-performance higher-performance self-assigned this Apr 10, 2023
@higher-performance
Copy link
Collaborator

higher-performance commented Apr 10, 2023

Thanks for confirming. I think I may have tracked it down. The problem indeed seems to stem from InvokeWith, and it's exacerbated with libc++ (which those toolchains use by default). Specifically, the lambdas inside it (passed to Cleanup) are re-instantiated for every template argument combination InvokeWith is instantiated with. Since lambda types are distinct, when they're implicitly converted to std::function objects, this results in multiple std::function::function instantiations with different lambda types passed to the constructor.

They require using functors outside the templates. I'm looking into that now.

kunitoki pushed a commit to kunitoki/googletest that referenced this issue Nov 4, 2023
The slowdown appears to be due to an implicit conversion of distinct (yet semantically identical) lambdas to `std::function`. Lifting out the lambdas into functors that don't get re-instantiated reduces compilation times by nearly half.

Fixes google#4156

PiperOrigin-RevId: 523447948
Change-Id: Ib0ae0761a54d7b1f2b706b14b2858eedf47e2297
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants