I'm currently learning C++ on my own, and I am curious about how push_back()
and emplace_back()
work under the hood. I've always assumed that emplace_back()
is faster when you are trying to construct and push a large object to the back of a container, like a vector.
Let's suppose I have a Student
object that I want to append to the back of a vector of Students.
struct Student {
string name;
int student_ID;
double GPA;
string favorite_food;
string favorite_prof;
int hours_slept;
int birthyear;
Student(string name_in, int ID_in, double GPA_in, string food_in,
string prof_in, int sleep_in, int birthyear_in) :
/* initialize member variables */ { }
};
Suppose I call push_back()
and push a Student
object to the end of a vector:
vector<Student> vec;
vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
My understanding here is that push_back
creates an instance of the Student
object outside of the vector and then moves it to the back of the vector.
I can also emplace instead of push:
vector<Student> vec;
vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
My understanding here is that the Student object is constructed at the very back of the vector so that no moving is required.
Thus, it would make sense that emplacing would be faster, especially if many Student objects are added. However, when I timed these two versions of code:
for (int i = 0; i < 10000000; ++i) {
vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
}
and
for (int i = 0; i < 10000000; ++i) {
vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
}
I expected the latter to be faster, since the large Student object wouldn't have to be moved. Oddly enough, the emplace_back
version ended up being slower (across multiple attempts). I also tried inserting 10000000 Student objects, where the constructor takes in references and the arguments in push_back()
and emplace_back()
are stored in variables. This also didn't work, as emplace was still slower.
I've checked to make sure that I'm inserting the same number of objects in both cases. The time difference isn't too large, but emplacing ended up slower by a few seconds.
Is there something wrong with my understanding of how push_back()
and emplace_back()
work? Thank you very much for your time!
Here's the code, as requested. I'm using the g++ compiler.
Push back:
struct Student {
string name;
int student_ID;
double GPA;
string favorite_food;
string favorite_prof;
int hours_slept;
int birthyear;
Student(string name_in, int ID_in, double GPA_in, string food_in,
string prof_in, int sleep_in, int birthyear_in) :
name(name_in), student_ID(ID_in), GPA(GPA_in),
favorite_food(food_in), favorite_prof(prof_in),
hours_slept(sleep_in), birthyear(birthyear_in) {}
};
int main() {
vector<Student> vec;
vec.reserve(10000000);
for (int i = 0; i < 10000000; ++i)
vec.push_back(Student("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997));
return 0;
}
Emplace back:
struct Student {
string name;
int student_ID;
double GPA;
string favorite_food;
string favorite_prof;
int hours_slept;
int birthyear;
Student(string name_in, int ID_in, double GPA_in, string food_in,
string prof_in, int sleep_in, int birthyear_in) :
name(name_in), student_ID(ID_in), GPA(GPA_in),
favorite_food(food_in), favorite_prof(prof_in),
hours_slept(sleep_in), birthyear(birthyear_in) {}
};
int main() {
vector<Student> vec;
vec.reserve(10000000);
for (int i = 0; i < 10000000; ++i)
vec.emplace_back("Bob", 123456, 3.89, "pizza", "Smith", 7, 1997);
return 0;
}
emplace_back
, but only one topush_back
. – Valuablestd::strings
from the string literals each time. – Internevec.push_back(Student("Bob"...
Student()
creates a temporary object, and push_back() moves it, if there is a useful move operator. For most "large" objects, their content is something that can be usefully moved (eg std::string or nested containers), and the cost compared to emplace is marginal. I think @Adrian might be onto something as well. If the object can't be moved, then it could be constructed just once and some processing saved. – Maxabenchmark::DoNotOptimize
orbenchmark::ClobberMemory
(when appropriate) to prevent the compiler from optimizing the variable away. Like so: quick-bench.com/5tQMkMwC5-j4nmUYSsqcmWeNEPs (note that optimizations are on) – Jerky