Want to read Slashdot from your mobile device? Point it at m.slashdot.org and keep reading!

 



Forgot your password?
typodupeerror
×

Comment Re:Write-only code. (Score 1) 757

I have no idea why you are arguing but saying EXACTLY the same things I am.

I am not saying to make a and b into unique pointers to a copy. I am saying "a and b ARE LOCAL VARIABLES!!!!!" They will be copied to make the lambda, it is NOT POSSIBLE to avoid this!!!! The function can return before the lambda is destroyed. And you seem to think "constructing on the stack" does not involve a copy of a and b, which is wrong. You do mention the "move" which does do another copy (though move semantics could cause a more-efficient version but it is not zero). Actually the lamda data structure is created on the heap because this is more efficient than the move.

The rest of my comments were about how the C++ compiler will actually do better than your attempts at premature optimization by forcing a and be to be on the heap. There will be only a single "shared pointer" to the lambda object, not one to a and another to b. Also what boost calls an "intrusive ptr" will be used, avoiding a lot of overhead of std::shared_ptr. And as my C code shows, it is possible to avoid multiple references to the lambda object, thus a unique_ptr could be used, though I believe this will require the optimizer to have access to the implementation of the thread constructor so it knows the lambda is not copied.

Comment Re:Write-only code. (Score 1) 757

Above AC is an excellent example of the problems with C++. He has quite a few misconceptions.

a = std::make_shared(x) does make a local shared pointer, but not the data itself, which is allocated on the heap.

The lambda absolutely does use the equivalent of a unique ptr. There is a block of memory allocated and a and b are copied to it (this block also contains a pointer to the actual code, which in the example will be something to further copy or move a and b to the stack and call the do_something function). This is the copy that is unavoidable. This block is freed when the pointer goes out of scope. Since it is passed by value move semantics mean that there is never more than one pointer, so it is concievable that the optimizer will do a unique ptr to it (though it is likely that something more like a shared ptr is done, or the boost intrusive_ptr).

Yes you can force it to use std::move but this should be an automatic optimization, it is nonsense that I have to type that. But even a move is much less efficient than direct use. The block is freed when no longer needed by the execution of the lambda (in the parallel thread).

I do not want to use a and b in the parent thread. That is the whole point. Way to get completely confused there!

Comment Re:Write-only code. (Score 1) 757

You mean the caller has to do something like this (not sure of the syntax and I think it requires C++17)?

  std::thread([std::move(a), std::move(b)](){do_something(a,b);}).detach();

Not sure if that is a good advertisement for C++.

It would be nice if this happened automatically when possible, but apparently for complex language rule reasons it cannot. The following code must make a copy of A:

  void f() {
          ComplexThing A(FunctionReturningComplexThing()); // move
          DoSomething(A); // the copy is here
  }

While this code, which seems like it should be what the above optimizes to, will only use move:

  void f() {
          DoSomething(FunctionReturingComplexThing());
  }

That is annoying and the fact that such optimizations are not allowed is a good sign that there are problems with the design of C++.

Comment Re:Write-only code. (Score 1) 757

That will not work out well if a and b are local variables. You will have to make a copy in order to make the std::shared_ptr point at them, so just as many copies are done as before (the second copy is when *a is copied to the argument to do_something, and, as before, can be avoided by making do_something take a const reference.

The basic lambda [=] syntax will work better. First it makes only one pointer to a sort of box containing the copies of both a and b, rather than two pointers. Also it uses something much more like std::unique_ptr which is much more efficient.

Comment Re:C++ is hard (Score 1) 757

I think you are right that placement_new could be used to get a block of memory filled with the object without using malloc and without double-indirection when using it. It looks like every method on that static object has to be copied to the dummy object, so I'm not sure if that is a good selling point for C++.

What I was thinking of was some keyword added to the static that causes no change in any code except the destructor is not called. An idea I had was to use '&' without constructor args after it:

      static Foo&; // uses default constructor
      static Foo&(1,2,3); // uses some other constructor

However I am rather worried that this may collide with some existing syntax.

I never heard of a guarantee that statics are destroyed in the opposite order of creation. In fact this seems to be completely false in cases where a function containing a static variable is first run in a parallel thread. Wrapping statics in functions is useful to guarantee construction order, and I do it all the time, but never used it to control destruction order.

Even if destruction order could be controlled, it does not fix the real problem where the static object obtains a pointer to an object that was constructed later, generally for caching. An example is an OpenGL resource, you want your destructor to release the resource but that will crash if the OpenGL context has been destroyed. Adding an if statement to the destructor that is only true when your program is exiting is pretty distasteful.

Comment Re:Write-only code. (Score 1) 757

No, the lambda must not take the arguments by reference. This is because the original values can be destroyed before the lambda is run, invalidating the references.

do_something can take them by reference, the lambda calls it and the lambda is not destroyed until after it returns.

I think the job of figuring out which is more efficient should belong to the compiler, but this would require C++ to be redefined such that all arguments are possibly const references (ie a function cannot modify it's own arguments, or perhaps modification forces a copy inside the function).

Comment Re:Ahhhh, C++ (Score 1) 757

auto copy = string{mystring}.replace("from", "to"); and move semantics avoids the extra copy.

The string constructor does an unnecessary copy. You are correct that move semantics avoids yet another copy from the result to "copy". I have not found any way to do this except by having two different functions, one which does an in-place modification and another that returns a newly constructed string. This produces questions about how to name them, as only one of them gets the "good" name.

I have seen attempts to make a "modstring" subclass where the methods happen in-place. Not sure if this is a great idea.

I suspect disagreement about how to do this is why all these useful functions have not been added to strings.

In any case I also apologize, your style of in-place modification would not prevent reference-counting implementations. The problem is operator[] only. If in fact you changed characters with a string.replace_char(n,c) it could be done with reference counted strings.

Comment Re: Write-only code. (Score 2) 757

His code is constructing a lamda as a local value, not copying it anywhere, and directly calling it, then destroying it. When the call is done every detail of the lambda is known precisely, and this can be optimized (apparently g++ does so, too).

The original post constructs a lambda and passes it by value to the thread constructor and then exits before the lambda is used. This requires a and b to be copied. Later the forked thread executes the lambda. It is highly unlikely the locations of a and b in the lamda structure are in the memory location that the lambda function looks for them, so they must be copied (and memmove is explicitly not allowed by C++, you must use the copy or move constructor).

The fix is to make do_something take the arguments as const references, which are really pointers, and then the lambda caller can just make the pointers point at where a and b are in the lambda structure.

C++ would have been helped considerably if all arguments to functions were const references (with the compiler allowed to choose whether to copy or make a reference depending on which is more efficient). You could use volatile to make a non-const reference if needed (though most code I have seen use a pointer for out parameters). This apparently would break too much code, but is by far the biggest source of unexpected inefficiency in C++ and really should be fixed rather than having the code writers decide whether a copy or reference is faster.

Comment Re:Write-only code. (Score 5, Informative) 757

The sample code will copy a and b twice, once to put them in the lambda closure

Only without optimization flags enabled. Otherwise the lambda will be inlined in most cases.

BZZT! Wrong! Think: what happens if a and b are local variables and the function creating the thread returns before the lambda runs? They must be copied to somewhere that is not destroyed by the caller returning. That copy is not in the correct location because they are created before the thread stack, so another copy is unavoidable. The only way to fix it is to make do_something take const references. Though it is true that if do_something was inline it would probably fix it.

Show me the code

Yes it is ugly and I never claimed otherwise. The problem is that C++ compiles into the equivalent of this and it is hidden behind the scenes. Here is is pretty obvious that I must not pass a pointer to a or b, not so clear in C++:

#include <pthread.h>
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>

void do_something(int a, int b) {
    printf("do_something %d %d\n", a, b);
}

typedef struct {
    int a, b;
} lambda_args;

void* lambda_run (void *p) {
    lambda_args* v = (lambda_args*)p;
    do_something(v->a, v->b);
    free (v);
    return 0;
}

void thread_do_something (int a, int b)
{
    lambda_args* v = (lambda_args*)malloc(sizeof(lambda_args));
    v -> a = a;
    v -> b = b;
    pthread_t thread;
    pthread_create (&thread, 0, lambda_run, v);
}

int main()
{
    thread_do_something(1, 2);
    thread_do_something(3, 4);
    sleep (1);
    return 0;
}

Comment Re:C++ is hard (Score 1) 757

assumptions about the order of static variable destructors

Yes this is a huge problem. Almost all of our code has static objects defined something like this:

    static Foo& theStaticFoo = new Foo(2);

This is so the destructor is not run when the program exits, since it will usually crash because Foo relies on some other object that has already been destroyed.

An annoyance of this is that a non-optimizing compiler will do an extra level of indirection, and call malloc at startup.

It would be nice if C++ added some kind of syntax to make a static object that does not call the destructor without making construction or use less efficient. Possibly somehow sticking the much-overused "const" keyword in there somewhere?

Slashdot Top Deals

Intel CPUs are not defective, they just act that way. -- Henry Spencer

Working...