Java Performance Services
Training, Seminars, Benchmarking, Tuning

Java Performance Tuning Course


Chania Crete, May 17-20, 2010


Expert led Training
Including admission to JFokus
Stockholm Sweden
January 25th-29th.

Sun Extreme Learning EXL-2025

Washington, Jan 5-8,2009
San Fransisco, Jan 11-14, 2009
Atlanta, Feb 8-11, 2010
Tokyo, March 1-5, 2010
Hong Kong, TBD



San Francisco, January 11-14

Anti-if

I have joined Anti-IF Campaign

Calendar

««Feb 2010»»
SMTWTFS
  123456
78910111213
14151617181920
21222324252627
28

Performance Anti-Patterns

My Top Tags

                                       

Mailing List

My RSS Feeds








Thread-safe non-synchronized read/writes?

posted Monday, 27 October 2008

I got into a discussion with Jevgeni and Heinz about some code that Jevgeni was working on. He needed threads to do something every 1/2 second. These thread could apparently look at the timer very very frequently so he didn't want to synchronize. But without synchronization you don't have thread safety do we. Well maybe we do. The clearist way to achieve this is to declare the variable volatile. But doing so will cause the thread to reload the value by-passing cache and this is something Jevgeni wanted to avoid. Borrowing an idea from Cliff Click's non blocking concurrency work I suggested something like this

 

int counter;

volatile int writefence;

public void run() {

    // do stuff

   counter += 1;

  writefence++;

}

 

public int getCounter() {

    return this.counter;

}

 

So, the thread responsible for maintain counter will change that value very infrequently in Jevgeni's use case. When it does write, it will then write to a volatile int. That write should cause the cache to flush values out to memory. The next read will refresh the CPU cache with a current value. So whle some read threads may slip through, it's not important in Jevgeni's case because they treads visit often. What more important, the writer working slowly only causes the disruption in the CPU infrequently where as the readers never trigger a refresh. Now to think about how this is broken... if it is.

 




1. Matthias left...
Tuesday, 28 October 2008 7:53 am

I know you know better than this. You have no garantuee that the new counter value will ever arrive on the reading side. #getCounter might get inlined and since the read to this.counter doesn't carry any synchronization, it might get elided or moved out of a loop. As always, you need to construct a logical causality (or happens-before) between the write and the read.

"But doing so will cause the thread to reload the value by-passing cache". How did you arrive at that conclusion? It means the value won't come from a register but might well come from the cache on a cache-coherent system.

Third, where is the measurement that showed this is your bottleneck? I suspect idle speculation at work here...


2. Kirk Pepperdine left...
Tuesday, 28 October 2008 9:01 pm

@Matthias

The CAS makes sure the value is visible. Otherwise you'd be perfectly correct. In this case we do have a benchmark that demonstrates a choke point in the product and this technique minimizes the problem. So while in many cases you'd be right, in this particular case, there is more going on the idle speculation. Performance is like death by paper cuts, there are an infinite number of edge cases.

Kirk


3. Matthias left...
Tuesday, 28 October 2008 9:07 pm

Hmm. I don't see a CAS here. Maybe your example is not showing everything. But anyway, you always need synchronization at both ends. There's no way around it. Enough inlining and optimization could otherwise turn the reader into "int counter = this.counter; while(mainloop) { // counter is constant here }"


4. Matthias left...
Wednesday, 29 October 2008 8:12 pm

To put some meat to the discussion: is the following what you have in mind? If so, it doesn't work. Doesn't print anything for me. When I make counter volatile, it does.

public class Hugh {

  • int counter;

  • volatile int writefence;

  • public void run() {

    • try {

      • Thread.sleep(1000);

    • } catch (InterruptedException e) {

    • }

    • counter += 1;

    • writefence++;

  • }

  • public int getCounter() {

    • return this.counter;

  • }

  • private static void doSomethingElse() {

  • }

  • public static void main(String[] args) {

    • final Hugh hugh = new Hugh();

    • new Thread() {

      • @Override

      • public void run() {

        • while(true) {

          • hugh.run();

        • }

      • }

    • }.start();

  • new Thread() {

    • @Override

    • public void run() {

      • int lastCounter = 0;

      • while(true) {

        • int nextCounter;

        • while((nextCounter = hugh.getCounter()) == lastCounter) {

          • doSomethingElse();

        • }

        • System.out.println("Counter updated to " + nextCounter);

        • lastCounter = nextCounter;

      • }

    • }

  • }.start();

  • }

}


5. cronin left...
Thursday, 30 October 2008 7:36 am

What about statements reordering? I believe that compiler/hotspot is free to change

counter++; writefence++;

into

writefence++; counter++;

May be reasonable to write

{

  • counter++;

} writefence++;

to avoid this reordering?


6. Matthias left...
Thursday, 30 October 2008 4:00 pm

@cronin: > What about statements reordering? I believe that compiler/hotspot is free to change > counter++; writefence++; into writefence++; counter++;

No. Since the revision of the memory model, writing to a 'volatile' guarantees that no previous write will be reordered beyond it. Vice versa with reads. That's why you can use volatiles for synchronization purposes - but only if you both write it in one thread _and_ read it in the other. Not like Kirk tries here.


7. Christian Vest Hansen left...
Monday, 10 November 2008 10:09 pm

Matthias is right. While this trick might work on your particular CPU with your specific JVM, the memory model does not guarentee it to keep working if these variables change.

So Jevgeni sees a cost in volatile reads because his threads have a very high pulse and checks this variable very often. Why don't he keep a thread-local counter and only check the variable every, say, 1.024 pulse?


8. Paul Houle left...
Tuesday, 11 November 2008 8:48 pm :: http://gen5.info/q/

He's worried about the synchronization overhead of synchronizing every half second? What's he programming, a Commodore 64?


9. stanimir left...
Thursday, 11 December 2008 2:36 am

although the read counter may be properly updates since the volatile will issue a memory fence the reading thread is *not* guaranteed to see the change unless there is any memory fence before the reading. Checking volatile might be an issue but definitely not one for "high" pulse of half a second.