Skip to contentSkip to conversation

frlock and barrier discussion

DISCUSSION Manfred Spraul 4 participants 5 messages Jan 30, 2003

lore
Share & tools
lore
#0 lore raw

Stephen wrote:

[snip - memory barrier for fr_write_begin]

Quoted 1 line

Using mb() is more paranoid than necessary.

What about the memory barrier in fr_read_begin?
If I understand the Intel documentation correctly, then i386 doesn't need them:
"Writes by a single processor are observed in the same order by all processors"

I think "smp_read_barrier_depends()" (i.e. a nop for i386) is sufficient. Attached is a test app - could someone try it? I don't have access to a SMP system right now.

What about permitting arch overrides for the memory barriers? E.g. ia64 has acquire and release memory barriers - it doesn't map to the Linux wmb()/rmb() scheme.

--
Manfred

/*
* frlock: test for Intel memory ordering.
* Copyright (C) 1999,2003 by Manfred Spraul.
*
* Redistribution of this file is permitted under the terms of the GNU
* Public License (GPL)
* $Header: /pub/home/manfred/cvs-tree/movopt/frlock.cpp,v 1.2 2003/01/26 10:41:39 manfred Exp $ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>

static volatile int g_val1;
static volatile int g_val2;
static volatile int g_seq1;
static volatile int g_seq2;

static volatile int start;

#define MB()	__asm__ __volatile__ ("lock;addl $0,(%%esp)\n\t" \
					:/* no output*/ \
					:/* no input*/:"cc","memory")

#define DELAY() do { int i; for(i=0;i<1000;i++); } while(0)

void* threadfnc(void* param)

{
	while(!start);
	if(1 == (int)param)
		goto cpu1;
	if(2 == (int)param)
		goto cpu2;
	assert(0);
cpu1:
	{	// reader:
		for(;;) {
			int x1,x2,val1,val2;

			x1 = g_seq1;
			val1 = g_val1;
			val2 = g_val2;
			x2 = g_seq2;
			if (x1 == x2) {
				if (val1 != val2) {
					printf("Bad! memory ordering violation with %d/%d: %d/%d.\n", x1, x2, val1, val2);
				}
			}
	    	}
	}
cpu2:
	{	// writer:
	    	int target = 0;
		for (;;) {

			// write 1:
			target++;
			g_seq1 = target;
			g_val1 = target;
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 2:
			target++;
			g_seq1 = target;
			g_val1 = target;
			MB();
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 3:
			target++;
			g_seq1 = target;
			g_val2 = target;
			g_val1 = target;
			g_seq2 = target;
			DELAY();

			// write 4:
			target++;
			g_seq1 = target;
			g_val2 = target;
			MB();
			g_val1 = target;
			g_seq2 = target;
			DELAY();
			


			// write 5:
			target++;
			g_seq1 = target;
			g_val1 = target;
			MB(); MB();
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 6:
			target++;
			g_seq1 = target;
			g_val1 = target;
			MB(); DELAY();
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 7:
			target++;
			g_seq1 = target;
			g_val2 = target;
			MB(); MB();
			g_val1 = target;
			g_seq2 = target;
			DELAY();

			// write 8:
			target++;
			g_seq1 = target;
			g_val2 = target;
			MB(); DELAY();
			g_val1 = target;
			g_seq2 = target;
			DELAY();
		}
	}
}

void start_thread(int id)

{
	pthread_t thread;
	int res;

	res = pthread_create(&thread,NULL,threadfnc,(void*)id);
	if(res != 0)
		assert(false);
}

int main()

{
	printf("movopt:\n");
	start_thread(1);
	start_thread(2);
	printf(" starting, please wait.\n");
	fflush(stdout);
	start = 1;
	for(;;) sleep(1000);
}
#1 lore raw
Quoted 7 lines · Manfred Spraul

Stephen wrote:

[snip - memory barrier for fr_write_begin]

Quoted 1 line

Using mb() is more paranoid than necessary.

Quoted 7 lines

What about the memory barrier in fr_read_begin?
If I understand the Intel documentation correctly, then i386 doesn't need them:
"Writes by a single processor are observed in the same order by all processors"

I think "smp_read_barrier_depends()" (i.e. a nop for i386) is sufficient.

I don't see what you mean, there is no dependency we can rely on between the read of the sequence number and the critical section reads, the critical section reads has nothing to do with the sequence number reads and the frlock itself.

Quoted 167 lines

Attached is a test app - could someone try it? I don't have access to a SMP system right now.

What about permitting arch overrides for the memory barriers? E.g. ia64 has acquire and release memory barriers - it doesn't map to the Linux wmb()/rmb() scheme.

--
Manfred

/*
* frlock: test for Intel memory ordering.
* Copyright (C) 1999,2003 by Manfred Spraul.
*
* Redistribution of this file is permitted under the terms of the GNU
* Public License (GPL)
* $Header: /pub/home/manfred/cvs-tree/movopt/frlock.cpp,v 1.2 2003/01/26 10:41:39 manfred Exp $ */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>

static volatile int g_val1;
static volatile int g_val2;
static volatile int g_seq1;
static volatile int g_seq2;

static volatile int start;

#define MB()	__asm__ __volatile__ ("lock;addl $0,(%%esp)\n\t" \
					:/* no output*/ \
					:/* no input*/:"cc","memory")

#define DELAY() do { int i; for(i=0;i<1000;i++); } while(0)

void* threadfnc(void* param)

{
	while(!start);
	if(1 == (int)param)
		goto cpu1;
	if(2 == (int)param)
		goto cpu2;
	assert(0);
cpu1:
	{	// reader:
		for(;;) {
			int x1,x2,val1,val2;

			x1 = g_seq1;
			val1 = g_val1;
			val2 = g_val2;
			x2 = g_seq2;
			if (x1 == x2) {
				if (val1 != val2) {
					printf("Bad! memory ordering violation with %d/%d: %d/%d.\n", x1, x2, val1, val2);
				}
			}
	    	}
	}
cpu2:
	{	// writer:
	    	int target = 0;
		for (;;) {

			// write 1:
			target++;
			g_seq1 = target;
			g_val1 = target;
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 2:
			target++;
			g_seq1 = target;
			g_val1 = target;
			MB();
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 3:
			target++;
			g_seq1 = target;
			g_val2 = target;
			g_val1 = target;
			g_seq2 = target;
			DELAY();

			// write 4:
			target++;
			g_seq1 = target;
			g_val2 = target;
			MB();
			g_val1 = target;
			g_seq2 = target;
			DELAY();
			


			// write 5:
			target++;
			g_seq1 = target;
			g_val1 = target;
			MB(); MB();
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 6:
			target++;
			g_seq1 = target;
			g_val1 = target;
			MB(); DELAY();
			g_val2 = target;
			g_seq2 = target;
			DELAY();

			// write 7:
			target++;
			g_seq1 = target;
			g_val2 = target;
			MB(); MB();
			g_val1 = target;
			g_seq2 = target;
			DELAY();

			// write 8:
			target++;
			g_seq1 = target;
			g_val2 = target;
			MB(); DELAY();
			g_val1 = target;
			g_seq2 = target;
			DELAY();
		}
	}
}

void start_thread(int id)

{
	pthread_t thread;
	int res;

	res = pthread_create(&thread,NULL,threadfnc,(void*)id);
	if(res != 0)
		assert(false);
}

int main()

Quoted 9 lines
{
	printf("movopt:\n");
	start_thread(1);
	start_thread(2);
	printf(" starting, please wait.\n");
	fflush(stdout);
	start = 1;
	for(;;) sleep(1000);
}

Andrea

#2 lore raw
Quoted 16 lines · Andrea Arcangeli
Quoted 13 lines · Manfred Spraul

Stephen wrote:

[snip - memory barrier for fr_write_begin]

Quoted 3 lines

Using mb() is more paranoid than necessary.

What about the memory barrier in fr_read_begin?
If I understand the Intel documentation correctly, then i386 doesn't need them:
"Writes by a single processor are observed in the same order by all processors"

Quoted 9 lines
Quoted 3 lines

I think "smp_read_barrier_depends()" (i.e. a nop for i386) is sufficient.

I don't see what you mean, there is no dependency we can rely on between the read of the sequence number and the critical section reads, the critical section reads has nothing to do with the sequence number reads and the frlock itself.

You are right - "observed in the same order by all processors" only means that the memory interface of the cpus see all writes in order, not that instruction executed by the cpus will observe the writes in order.

That leaves ia64 with the acquire/release barriers.

--
Manfred

#3 lore raw
In reply to Andrea Arcangeli
Quoted 3 lines

Attached is a test app - could someone try it? I don't have access to a SMP system right now.

Try to put [g_val1, g_seq1] and [g_val2, g_seq2] on two different cache lines and run it on a SMP system using CPUs with a partitioned cache architecture. Even if you do an WMB on writer side, you might see a different order w/out an RMB on the reader side. This because the two cache lines might be committed to different partitions with different loads, and the latest ( in time order ) commit might see a fastest path due a lower traffic. An RMB on the reader side ( that is usually expensive ) wait for all CPUs's memory controllers to flush their stuff before resuming execution.

- Davide

#4 lore raw
In reply to Manfred Spraul
Quoted 2 lines · Manfred Spraul

If I understand the Intel documentation correctly, then i386 doesn't need them:
"Writes by a single processor are observed in the same order by all processors"

See the PPro errata. There are some constraints on this in the real world. You may need locked ops on the ppro and earlier cpus while being able to do it the fast way on PII and higher