--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
I want this:
std::string s = "foo";
std::vector<int> v = {1,2,3};
to be as efficient as this:
char s[] = "foo";
int v[] = {1,2,3};
I can't think of a real world use case where this would have a significant performance impact, and it adds significant complexity penalties into the core language.
>The main use case would be enabling the use of strings, vectors and other types that use dynamic storage as parameters, local variables and return types of constexpr functionsOk, I agree that's interesting. But your proposal started out with "I want [code sample] to be as efficient as [code sample]."
Supporting allocate-during-translation/deallocate-during-runtime is pretty tricky from an implementation perspective, especially since the usual allocation and deallocation functions can be replaced. We cannot know in advance what layout a custom heap will take in memory, so we cannot prepopulate a "preheap" as a static data structure and expect a replacement deallocation function to be able to cope with it. (Also, practically-speaking, the compiler vendor typically does not know how the library vendor will choose to implement the heap.) This can be mitigated by requiring the implementation to repeatedly call the (replacement) ::operator new to allocate the preheap during program startup, but that removes a lot of the value of the proposal.
More difficult is supporting types like std::vector, which want to manipulate bytes of storage, not just allocated objects. Constant expression evaluation very deliberately does not allow accessing the object representation of a type (that is, in many cases, impossible to implement during translation, because -- for instance -- the bit pattern of types containing pointers is not yet known). A suitably careful implementation of std::vector<T> could probably sidestep this issue, perhaps through 'new Uninitialized<T>[N];', where Uninitialized is a union type containing T, but it's unlikely that any such approach would be compatible with the other constraints on std::vector, such as guaranteed array-like contiguous allocation (so data()[N] works) and use of a custom allocator.
Having said the above, I did come up with a design for this which I believe works. It's somewhat more restricted than what you were describing, but is enough to get many uses of dynamic storage to work:* In order to support deleting objects in a destructor, allow destructors to be marked 'constexpr', and implicitly mark all trivial destructors as 'constexpr'. Require a literal type to have a constexpr destructor rather than a trivial one.* Allow new-expressions and delete-expressions in core constant expressions. The rules for constant expressions are unchanged, so they can still only refer to objects of static storage duration (not of automatic, thread, or [now possible] dynamic storage duration). This allows temporary usage of dynamic allocation during constant expression evaluation, but does not allow it to leak outside the computation. Under N3664, and implementation is permitted to elide this allocation rather than actually invoking the allocation/deallocation functions (and during constant expression evaluation, it would be expected to do so).* Objects declared 'constexpr' can have non-trivial (but constexpr) destructors, but only if the evaluation of the destructor on the object is itself a constant expression (since the object is 'const', we can check this during translation). For such an object, we allow subobjects to be pointers or references to dynamic storage, so long as the evaluation of the destructor deletes the storage. Additionally, make it undefined behavior to delete such objects outside of the destructor.
string(const CharT *str) constexpr
: _M_p(_allocdup(str))
{ }
CharT *allocdup(const CharT *str) constexpr
{
auto result = static_cast<_M_Internal *>(operator new (strlen(str) +
HeaderSize, std::predynamic));
new (result) _M_Internal(str);
return result;
}
Please, no. The object s is constexpr and the memory it allocated cannot be
deleted at runtime. The preheap is the read-only data segment of the
executable, not the normal heap.
It's quite impossible for the compiler to create a preheap in the model you're
describing, since the compiler doesn't know what malloc() or operator new()
do. The standard allows operator new() to be overridden by the user.
The only way to call the user's operator new() would be at runtime and we
already have that solution in the form of dynamic initialisation at load time.
>> It's quite impossible for the compiler to create a preheap in the model
>> you're
>> describing, since the compiler doesn't know what malloc() or operator new()
>> do. The standard allows operator new() to be overridden by the user.
>
> No, it is possible. Preheap objects are represented during translation in
> the same way as literal temporaries, local variables in constexpr functions
> and constexpr objects of static storage duration. At load-time preheap
> objects are automatically loaded into the heap. As Richard suggests
> implementations can allocate run-time storage for preheap objects at
> load-time using the allocation functions at that time (overridden or not) -
> although they would be free to elide the "copying" you are imagining in
> favor of more efficient ways given sufficient integration/intelligence of
> the different components of the implementation. But _with or without_ such
> load-time copying, it is still enormously useful. I wouldn't get too hung
> up on the particular mechanism and overlook the larger utility.
It might be useful if you describe what you expect to happen during
compilation, during load time, during runtime, and during unload time. The way
I am currently seeing things, you're asking for a lot in the compiler for very
little benefit: since the allocation must be redone at load-time, there's no
gain.
Let's take your simple but not trivial constructor from a few emails back:
String(const char* s)
: len(strlen(s))
, p(new char[len+1])
{
for (size_t i = 0; i < len+1; i++)
p[i] = s[i];
}
And we have the following global declaration:
constexpr String mystring("Hello");
During compilation, what will the compiler do? The first thing it needs to do
is call strlen("Hello"). Let's assume that it is constexpr, so the compiler
can know that it will return 5.
Now we have a call to ::operator new(6). What will the compiler do?
I'm going to assume the compiler allocates a "preheap" -- that is, some static
storage of length 6. The compiler knows the return value since it allocated
the address. After that, it will execute the loop.
At the end of the function, it could conclude that the memory layout should be
(pseudo-assembly):
.section .rodata, read-only, sharable
Lalloc_size_6:
.asciz "Hello"
mystring:
.quad 5
.quad Lalloc_size_6
If nothing happened at load time, we would have a perfectly valid String
object, with len == 5 and p pointing to a read-only section of memory.
Obviously, you can't delete p. This is already enormously useful for QString,
which uses copy-on-write semantics. All we need is a flag indicating that this
string is immutable, which QString has.
std::string has no such semantic, true. But all it requires to support an
immutable string data is a special allocator template, which causes the
destructor to be empty and the move constructor to be equal to the copy
constructor. Since the object is already const, no mutator functions can be
called, so no reallocation will ever happen.
Now, you said "At load-time preheap objects are automatically loaded into the
heap." That means the compiler must emit load- and unload-time functions like:
void init()
{
decltype(mystring.p) tmp = new char[6];
std::memcpy(tmp, mystring.p, 6);
mystring.p = tmp;
}
void fini()
{
delete[] mystring.p;
}
The second consequence is that the "mystring" object cannot be placed in a
read-only & sharable section of memory, since we needed to modify mystring.p.
The compiler needs to place this constexpr object in a read-write section of
memory. At best, with the help from the program loader, the memory page can be
set to read-only after initialisation (similar to the GNU -z relro solution).
Here's my question: what's the benefit? If we replace that constexpr with a
simple const keyword, current C++98 code would generate the following load-
and unload-time functions (after inlining the constructor and destructor):
I'm questioning the need to reload preheap objects into the real heap. That
kills most of the benefit of the functionality.
No, not what I am proposing. You are focusing on the mechanism and not the logical behaviour.
it doesn't matter that the "Hello\0" is in .rodata or .preheap; something needs
to know its address and allocate memory for it.
Again, I'm not questioning the benefit of the feature of using operator new()
in constexpr. I am questioning the "reallocate preheap into real heap at load-
time".
On quarta-feira, 16 de outubro de 2013 22:39:12, Andrew Tomazos wrote:
> Such an architecture would use less resources. Placing preheap objects in
> the heap allows them to be deleted during run-time, reclaiming their
> resources. If we placed them in .rodata they could not be deleted.
It actually uses a different type of resource. Data stored in .rodata is
sharable, always clean memory that the OS virtual memory manager can discard
at will, since it can simply reload it from disk.
The regular heap, however, is unsharable memory that can become dirty (it can
be written to). If the OS virtual memory manager needs to reclaim pages and
this page is dirty, it will first need to write to the swap / pagefile.
In that sense, regular heap is more expensive to store the same bytes.
Add to that:
- the overhead that malloc() has -- 8 to 32 bytes per allocation, depending
on the implementation
- the cost of calling malloc() during load-time (startup performance penalty)
> typedef int T;
>
> constexpr T* p = new T(3);
>
> static_assert(*p == 3);
>
> int main()
> {
>
> /* loader() */
> first_half();
> delete p;
> second_half();
>
> }
We don't need new in constexpr for that. The above program becomes better if
we replace constexpr with const, plus it will work with C++98.
On Thursday, October 17, 2013 8:43:23 AM UTC+2, Thiago Macieira wrote:
In that sense, regular heap is more expensive to store the same bytes.
Add to that:
- the overhead that malloc() has -- 8 to 32 bytes per allocation, depending
on the implementation
- the cost of calling malloc() during load-time (startup performance penalty)
Those overheads are implementation-dependant and generally not asymptotically significant.
Erm, *none* of these things are asymptotically sufficient.
All of this talk about binary sections sounds awfully platform specific for the standard at this point. You're talking about ELF sections, right? Not all platforms use ELF. As I said earlier, none of this is going to be significant in run time in the real world. If the point is to allow the types to be constexpr that's interesting, but the question there is "how does the constexpr language feature interact with operator new inside string/vector", not where in the binary various things go. The standard defines language semantics, not binary layout.
Another possible implementation would be to have no binary preheap concept. Instead, it is transformed by the compiler to generate the correct memory state at run time, which may involve calls to operator new. If involved in something constexpr, the compiler could figure out the right answers, but the behavior under constexpr and non constexpr conditions could be completely different.