Rationale behind copy semantics in STL containers.

T

Tony

peter koch said:
Tony skrev:

They certainly can't be a container implementation issue. At best it
can be a memory allocation issue (requiring subsequent allocation to be
sequential in memory),

I consider that part of the container architecture. But yes, it's more
appropriately categorized as memory management.
but that would require that those allocations
happens in an optimal order - e.g. allocate element n before element n
+1 and require no allocations in between.

Well for large datasets the clustering would be important. Otherwise not.
But aren't you really only talking about vector and not node based
containers in comparison? vector is special case.

Tony
 
M

Mirek Fidler

Tony said:
But aren't you really only talking about vector and not node based
containers in comparison? vector is special case.

Very important special case. In fact, node-based containers (like
std::list) are almost always outperformed by vector in any real piece
of code. Continual storage simply wins.

And do not forget that it also applies to deque.

Mirek
 
M

Mirek Fidler

peter said:
Well, I do not believe you should call it "just one extra level of
indirection". It is something that easily equates to a factor of 10 (or
more) in performance on modern processors (due to cache-misses).

Being curios, I decided to put this into the test in my recent
benchmark U++ Core benchmark

http://www.ultimatepp.org/www$uppweb2$vsstd$en-us.html

because testing is quite simple for U++ - I have just replaced
VectorMap by ArrayMap.

And the result was - in this test-case, it did not make any difference.

I believe you would get similar results for any nontrivial T.

Mirek
 
P

peter koch

Tony skrev:
[snip]
Well for large datasets the clustering would be important. Otherwise not.
But aren't you really only talking about vector and not node based
containers in comparison? vector is special case.

Well ... for small datasets, nothing really matters much. And I agree
that my arguments are relevant mostly for std::vector and std::deque,
but it is still relevant for other containertypes - it will be one
extra indirection and one more strain on the cache.

/Peter
 
T

Tony

Mirek Fidler said:
Very important special case. In fact, node-based containers (like
std::list) are almost always outperformed by vector in any real piece
of code. Continual storage simply wins.

And do not forget that it also applies to deque.

I choose containers first based upon appropriateness of the abstraction.
I do performance optimization (perhaps shoe-horning into another
container type) only on a need-to basis. I tend to think of node-based
containers as "containers" and vector as "array". Of course if I were
to just put pointers to objects in a vector, it would be more "container"
like.

Tony
 
T

Tony

peter koch said:
Tony skrev:
[snip]
Well for large datasets the clustering would be important. Otherwise not.
But aren't you really only talking about vector and not node based
containers in comparison? vector is special case.

Well ... for small datasets, nothing really matters much. And I agree
that my arguments are relevant mostly for std::vector and std::deque,
but it is still relevant for other containertypes - it will be one
extra indirection and one more strain on the cache.

I just wanted to verify that there wasn't something inherent in reference
implementation, other than another level of indirection, that was going
on behind the scenes. The cache thing I can manage.

Tony
 
M

Mirek Fidler

Tony said:
I choose containers first based upon appropriateness of the abstraction.

What abstract operations vector lacks and list has? (OK, there are some
maybe, but in 99.9% cases, vector abstraction is superset of others).
(OK2, I am not using std::vector, but U++ Vector and Array, so my
abstraction options are a little bit wider ;)

Mirek
 
T

Tony

Mirek Fidler said:
What abstract operations vector lacks and list has? (OK, there are some
maybe, but in 99.9% cases, vector abstraction is superset of others).
(OK2, I am not using std::vector, but U++ Vector and Array, so my
abstraction options are a little bit wider ;)

I meant more like: "I need an associative container where order counts,
hence
I'll choose a tree-based map container". In your example, vector vs. list,
"list" to me has much more connotation than vector ('vector', to me, sounds
more like something that has magnitude and direction than something
container-like btw).

I've heard it said that one should use vector unless there's good reason not
to, but I think it is better to choose a container that "looks and feels
right"
given the context. A lot of programs today have much more leeway because
the processor performance is so good. For instance, I use a map to map
platform window handles to my window class. How many windows is
a GUI program likely to have? 100? 1000? Those are trivial numbers
compared to what containers are designed for. I could stuff all those
key-value pairs into an array and search through all of them all of the time
and it probably wouldn't make one bit of difference to the user. But I
choose
a map anyway because it "looks and feels right".

Tony
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

I meant more like: "I need an associative container where order counts,
hence I'll choose a tree-based map container". In your example, vector vs.. list,
"list" to me has much more connotation than vector ('vector', to me, sounds
more like something that has magnitude and direction than something
container-like btw).

I always as myself this question when I need to choose a new container,
does the choice noticeable affect performance? If it isn't then I take
the container that allows me to do more with less code, in other words
the one with an interface and set of operations that best matches when
I need to do. And so, I think, does most of us in here.
I've heard it said that one should use vector unless there's good reason not
to, but I think it is better to choose a container that "looks and feels
right" given the context.

Well, it's up to you to decide what a "good reason" is, but I think
that what they mean is that if you need a container and don't have any
special requirements a vector is always to prefer, i.e. for many things
you could use a list just as well as a vector, in these cases go for
the vector.
 
M

Mirek Fidler

Tony said:
I meant more like: "I need an associative container where order counts,
hence
I'll choose a tree-based map container".

Sure, associative is another problem.
In your example, vector vs. list,
"list" to me has much more connotation than vector ('vector', to me, sounds
more like something that has magnitude and direction than something
container-like btw).

So you have the problem with the name? :)
I've heard it said that one should use vector unless there's good reason not
to

Actually, as long as it is not associative, there are no good reasons
to choose std::list.

Think is, std::list advantage seemingly is O(1) ability to
remove/insert element everywhere. But the trouble is that you must know
which one to remove/where to insert.

Maybe there are usage patterns which naturally lead to keeping some
iterator(s) into the middle of list, but I never met one.

So in most cases, you will want to find that position. And thanks to
node-based nature of list, iterating is way slower than std::vector, so
before you get your position, your O(1) insert/remove advantage is
lost.
A lot of programs today have much more leeway because
the processor performance is so good. For instance, I use a map to map
platform window handles to my window class. How many windows is
a GUI program likely to have? 100? 1000?

[off-topic]
A good GUI program will have about 1-10, as many as number of top-level
windows. Anyway, I am still using VectorMap to map handles to objects.
Just like you said, because it feels better ;)
[/off-topic]

100-1000 is quite a lot. You should always use something else than
linear search here.
Those are trivial numbers
compared to what containers are designed for. I could stuff all those
key-value pairs into an array and search through all of them all of the time
and it probably wouldn't make one bit of difference to the user. But I
choose
a map anyway because it "looks and feels right".

Yes, you are doing the right thing here. (Well, you can have
vector-like associative containers and they are much faster than binary
trees or other node-based maps, but that is another story ;)

Mirek
 
P

persenaama

Mirek said:
Think is, std::list advantage seemingly is O(1) ability to
remove/insert element everywhere. But the trouble is that you must know
which one to remove/where to insert.

That's hardly what most projects I've worked on use std::list or linked
lists in general. Often when linked list -like data structure is
chosen, it is NOT relevant at all what order objects are in the
container.

The primary concern is that you can create and delete objects
*cheaply*, and still have a way of keeping track of all objects you
have created. Deleting object from *middle* of container with array
semantics isn't very efficient.

The std::list has it's own problems, example, we want to keep track of
objects which are created with factory dp.

Example code:

texture* s = device->createTexture("bla.jpg");
s->release();

OK, let's assume device is the factory object which also manages
texture objects. The s->release() method implements reference counting
and when the reference count reaches zero delete is invoked.

If we keep the texture objects in std::list, finding objects for
deletion is iterating through the factory's texture object container
(or generic object container) until the value "s" is found.
Inefficient, this invites usage of std::map or similar container.

On the other hand, if the texture inherits from custom linked list
type, which *includes* (implementation detail, for example) pointers to
previous and next object in the sequence in the container, the removal
by value of s is trivial (and very fast). There should be a mechanism
to validate that s is actually part of the sequence it is removed from.
One way is to have pointer to the "current owner container" in the node
object.

class object
{
container* m_container;
object* m_prev;
object* m_next;
int m_refcount;
...
};

class texture : public object
{ ... };

But such design is more closer to what you might do in code you write
in C, not C++ .. it is a reasonable trade off to avoid writing custom
code for this kind of use case to just use std::set and be done with
it.

std::vector, or anykind of container with array semantics doesn't cut
it. We are NOT interested in inserting new objects in specific location
in the container, we just want to insert new stuff and remove *random*
objects out without significant overhead.

Maybe there are usage patterns which naturally lead to keeping some
iterator(s) into the middle of list, but I never met one.

Above you see one not very uncommon case where this might be
requirement. Believe it or not, std::set is MUCH better solution to
this particular problem to specificly AVOID having tangling iterators
as object handles, that would be plain stupid wouldn't it?

So in most cases, you will want to find that position. And thanks to
node-based nature of list, iterating is way slower than std::vector, so
before you get your position, your O(1) insert/remove advantage is
lost.

Speaking of iteration, of course, even with std::set for the use case
above, iterating the tree is still required. But that's precisely why
the tree is preferred over flat array or linked list.

The best is if pointer-to-object is fast to resolve into
pointer-to-node-in-sequence. The best case is possible if
pointer-to-object IS pointer-to-node-in-sequence (eg. custom code). But
profiling has revealed that it it not worth the effort for the kind of
uses *I* have put this kind of code into.


Sensible approach. Likewise. I choose the std::set for the task I used
to illustrational purposes for the reason that it scales well. For
small dataset, it doesn't matter what you use, it's fast anyways. This
scales pretty nice to medium complexity too. With really large amount
of objects, 1,000,000+ it would start to take noticeable hit compared
to "direct lookup", but the dataset will not be likely to reach that
size for for a while, past decade we have been hovering between 10 and
a few thousands, tops, depending on the application.

The tops figure cancels out the brute force linear search, is still
manageable without any significant runtime overhead to tree and doesn't
yet warrant custom code.

Yes, you are doing the right thing here. (Well, you can have
vector-like associative containers and they are much faster than binary
trees or other node-based maps, but that is another story ;)

While I agree with the sentiments that array is better caching wise, it
should only be concern if there is no other design criteria for
choosing a specific kind of container. For random or random-like access
the memory bandwidth isn't as relevant as latency, for caching point of
view. The latency can be amortized by design, don't use the data
immediately after reading and you won't stall. Example:

if ( object->foo )
bar( ... );

We have to read from "foo" which is behind a pointer, until we know if
we will branch or not. If the predicate says "true" here, we have to
call.. but we cannot make the call until the value is read, if it comes
off cache, we have high latency. On the other hand, it is possible to
work around memory latency with additional gates in the CPU, we could
decode ahead of the IP and have multiple look-ahead contexts, and
"branch" to the context that was actually taken after the predicate can
be resolved.

How far ahead we can execute depends on how much area we are willing to
sacrifice to implement this logic. There is likely to be much more
productive use for the gates as this problem can be solved on the
software side on performance critical sections of the code, which are
far and wide between most of the code we will ever have to write. How
did it go, 97.8735433 214 (?) % of code isn't performance critical?

Whatever.
 
M

Mirek Fidler

persenaama said:
That's hardly what most projects I've worked on use std::list or linked
lists in general. Often when linked list -like data structure is
chosen, it is NOT relevant at all what order objects are in the
container.

Wait a moment right there.

I was speaking about std::list as *value container*, not about list
structures in general.

List structures are great if you have to maintain groups of objects,
which logically belong elsewhere - which I think is exactly you are try
to explain. Heck, I even have a template class to make some object part
of such list.

But list *container* is next to useless.
If we keep the texture objects in std::list, finding objects for
deletion is iterating through the factory's texture object container
(or generic object container) until the value "s" is found.

Yep, that is what I meant.
On the other hand, if the texture inherits from custom linked list
type, which *includes* (implementation detail, for example) pointers to
previous and next object in the sequence in the container, the removal
by value of s is trivial (and very fast).

Yes, I agree. That is what I like to do as well ;)

Mirek
 
P

persenaama

Mirek said:
I was speaking about std::list as *value container*, not about list
structures in general.
...

But list *container* is next to useless.

Couldn't agree more! Also noticed my thoughts went to all kinds of
random directions, which is kind of lame. :(
 
S

Squeamizh

Roland said:
The probably best-selling STL-book, Scott Meyer's 'Effective
STL', not even mentions value semantics.

We must not agree on what value semantics means. See Item 3, "Make
copying cheap and correct for objects in containers."
 
T

Tony

Mirek Fidler said:
A lot of programs today have much more leeway because
the processor performance is so good. For instance, I use a map to map
platform window handles to my window class. How many windows is
a GUI program likely to have? 100? 1000?

[off-topic]
A good GUI program will have about 1-10, as many as number of top-level
windows. Anyway, I am still using VectorMap to map handles to objects.
Just like you said, because it feels better ;)
[/off-topic]

All the buttons and stuff are windows too.
100-1000 is quite a lot. You should always use something else than
linear search here.

I use a hash map.
Yes, you are doing the right thing here. (Well, you can have
vector-like associative containers and they are much faster than binary
trees or other node-based maps, but that is another story ;)

Tony
 
M

Mirek Fidler

Tony said:
am likely to have? 100? 1000?
[off-topic]
A good GUI program will have about 1-10, as many as number of top-level
windows. Anyway, I am still using VectorMap to map handles to objects.
Just like you said, because it feels better ;)
[/off-topic]

All the buttons and stuff are windows too.

Only if the library you are using is poorly designed ;)

Mirek
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,781
Messages
2,569,619
Members
45,316
Latest member
naturesElixirCBDGummies

Latest Threads

Top