Intel® oneAPI Threading Building Blocks
Ask questions and share information about adding parallelism to your applications when using this threading library.

Modification variable in operator() method

nuntawat
Beginner
700 Views
I would like to modify the value of the object inside operator method look like this:

[cpp]class Case {
  int sampleVar;
public:
  void operator() (const blocked_range& range) const {
    for(int i = range.begin(), i != range.end(), i++) {
      // do some tasks
      sampleVar++;
    }
  }

  Case(int sampleVar): sampleVar(sampleVar) {}
};[/cpp]

but because the operator method must be declared const keyword, so I cannot modify the value. What should I do?
0 Kudos
12 Replies
RafSchietekat
Valued Contributor III
700 Views
mutable int sampleVar;
0 Kudos
Dmitry_Vyukov
Valued Contributor I
700 Views
Quoting - Raf Schietekat
mutable int sampleVar;

AFAIR, functor can be executing and concurrently coping. If so, mutable is not the way. If only single variable of primitive type is copied, then mutable tbb::atomic can be used. Otherwise, mutex must be used in operator() and copy ctor.

0 Kudos
RafSchietekat
Valued Contributor III
700 Views
I don't know, I didn't feel like speculating about the context at this time, but I don't think I would try copying atomics myself, concurrently or otherwise.

Meanwhile, there's this pipeline thing that I don't care to look at again for at least a month, so maybe you could shine your light on that, too?
0 Kudos
Dmitry_Vyukov
Valued Contributor I
700 Views
Quoting - Raf Schietekat
I don't know, I didn't feel like speculating about the context at this time, but I don't think I would try copying atomics myself, concurrently or otherwise.


What do you mean?

0 Kudos
Dmitry_Vyukov
Valued Contributor I
700 Views
Quoting - Raf Schietekat
Meanwhile, there's this pipeline thing that I don't care to look at again for at least a month, so maybe you could shine your light on that, too?

There is so many unconnected questions/answers/discussions, what exactly moment do you mean?

0 Kudos
RafSchietekat
Valued Contributor III
700 Views
"What do you mean?" Maybe I should have another look at it (and explain the fifth line), but I wrote these comments in my "Additions to atomic" proposal:

// About copy constructors and copy assignment operators:
// Normally these must be defined together for consistency.
// Atomics cannot have copy constructors because that would also require the definition of a default constructor,
// which would interfere with the expectation that atomics are ready for use right after static initialization;
// any use of a(n implicitly defined) copy constructor should be avoided, however.
// They must have copy assignment operators to invoke store(), because, unlike the situation with constructors,
// a copy assignment operator will be implicitly defined even if another user-defined assignment operator exists,
// and selectively restricting assignment would only invite accidental use of a (non-atomic) implicit definition.
// TODO: double check the last statement

On a related topic, I also wrote this (reformatted):

// default-initialization does not translate to zero-initialization because atomic is non-POD
// because it has a copy assignment operator, which means that putting my_atomic()
// in an initializer list or doing "tbb::atomic a; a = tbb::atomic();"
// is incorrect (it doesn't do what it seems to be expected to do).

What do you think?

"There is so many unconnected questions/answers/discussions, what exactly moment do you mean?"

See "Pipeline" thread: I published a code snapshot, but it doesn't work yet, and I need to take a break from it myself to regain a fresh perspective.
0 Kudos
Dmitry_Vyukov
Valued Contributor I
700 Views
Quoting - Raf Schietekat
"What do you mean?" Maybe I should have another look at it (and explain the fifth line), but I wrote these comments in my "Additions to atomic" proposal:

// About copy constructors and copy assignment operators:
// Normally these must be defined together for consistency.
// Atomics cannot have copy constructors because that would also require the definition of a default constructor,
// which would interfere with the expectation that atomics are ready for use right after static initialization;
// any use of a(n implicitly defined) copy constructor should be avoided, however.
// They must have copy assignment operators to invoke store(), because, unlike the situation with constructors,
// a copy assignment operator will be implicitly defined even if another user-defined assignment operator exists,
// and selectively restricting assignment would only invite accidental use of a (non-atomic) implicit definition.
// TODO: double check the last statement

On a related topic, I also wrote this (reformatted):

// default-initialization does not translate to zero-initialization because atomic is non-POD
// because it has a copy assignment operator, which means that putting my_atomic()
// in an initializer list or doing "tbb::atomic a; a = tbb::atomic();"
// is incorrect (it doesn't do what it seems to be expected to do).

What do you think?



I think that functor's copy ctor must be defined explicitly:


struct functor
{
tbb::atomic x;
...
functor(functor const& f)
{
x.store(f.x.load(relaxed), relaxed);
}
};


0 Kudos
Dmitry_Vyukov
Valued Contributor I
700 Views
Quoting - Raf Schietekat
"There is so many unconnected questions/answers/discussions, what exactly moment do you mean?"

See "Pipeline" thread: I published a code snapshot, but it doesn't work yet, and I need to take a break from it myself to regain a fresh perspective.

I see the attachment in the last post, and I have skimmed over the thread, but I don't get what problem the attachment tries to solve, or what it tries to improve. What it's all about?

0 Kudos
RafSchietekat
Valued Contributor III
700 Views

"I think that functor's copy ctor must be defined explicitly" Maybe, but I just didn't want to speculate on what's going on here (I did think about suggesting "tbb::atomic *sampleVar;", but I decided instead to wait and see).

0 Kudos
RafSchietekat
Valued Contributor III
700 Views
Quoting - Dmitriy Vyukov

I see the attachment in the last post, and I have skimmed over the thread, but I don't get what problem the attachment tries to solve, or what it tries to improve. What it's all about?


Over to "Pipeline"...
0 Kudos
ARCH_R_Intel
Employee
700 Views
Yes, the functor body might be copied by the implementation of parallel_for. The simplest solution is pass sampleVar by reference and make sampleVar atomic, as shown below:

[cpp]class Case {   
  atomic& sampleVar;   
public:   
  void operator() (const blocked_range& range) const {   
    for(int i = range.begin(), i != range.end(), i++) {   
      // do some tasks   
      sampleVar++;   
    }   
  }   
  
  Case(atomic& sampleVar_): sampleVar(sampleVar_) {}   
}; [/cpp]
[cpp][/cpp]

Another option, which works if sampleVar is not being read until the parallel loop completes, is to use parallel_reduce. Using paralle_reduce this way may improve performance, because it allows you to accumulate in local copies of sampleVar and then sum them. Look at file examples/parallel_reduce/primes/primes.cpp in the TBB ditribution. Inside there is a class Sieve that tallies a counter variable "count". You can tally sampleVar in a similar way. Be sure to inspect how method Sieve(Sieve&&,split) initializes a local count and method Sieve::join sums two local counts.
0 Kudos
Alexey-Kukanov
Employee
700 Views
Another option, which works if sampleVar is not being read until the parallel loop completes, is to use parallel_reduce.

A side note: this reminded me of a suggestion I have heard earlier, which was for TBB algorithms, parallel_reduce in particular,to return a copy of the body object. This object can then be assigned to some variable, and it might have a conversion operator so that the following could be written:
int my_sum = parallel_reduce(blocked_range(0,N), MySummationBody(my_array), auto_partitioner());
0 Kudos
Reply