| Cobra | Inline Programs | multicore | ||
|---|---|---|---|---|
| NAMEmulti-core supportDESCRIPTIONThere are two predefined meta-variables (each with a possible synonym) available in inline programs:cpu or core -- the id of the current cpu core (0..Ncore) ncpu or ncore -- the number of available cpu cores (Ncore)There are further four predefined functions that specifically target multi-core executions. a_unify sum lock unlockThe number of cores used for processing Cobra programs is by default set to one. It can be set to a different value explicitly with a commandline argument. For instance, to use 16 cores: $ cobra -N16 -f prog.cobra *.[ch]and it can be modified interactively with a query command, e.g.: : ncore 16It can be evaluated, but not modified inside an inline program. 
	%{
		print ncore "\n";
		Stop;
	%}
The following collection of functions is for multi-core use. First there is a lock and unlock function for enforcing a single global lock on executions: lock() # make sure only one core can pass this lock unlock() # until the lock is released again with unlockTo see the need for the next two functions, remember that each core during a multi-core run will process a different portion of the input token sequence, and maintains separate copies of the variables and arrays that are constructed. For final processing, when we want a single core to collect all data stored by the different cores at a single core, we need to be able to either sum (integers) or unify (associative arrays) the data. For this we can use the predefined functions sum and a_unify. sum(varname) # add up all the values of an integer variable across cores sum(A[el]) # add up the values of integer array elements across cores a_unify(n) # unify all array data objects to be accesible on cpu n a_unify(a, n) # unify the contents of array a to be accesible on cpu nThe a_unify function sums the values of all integer elements, and for other types of elements (strings or token references) it stores only the first non-null entry found among all the cores running. That means that if multiple core store a string or a token reference at the same index in an associative array, only one of these values will survice unification. It also means that if integer values are stored, calling a_unify multiple times will corrupt the values, because a new sum over all elements is then computed. A simple example of the working of the sum function for a simple variable, when running with four cores: 
	: ncore 4
	: %{ if (@ident) { x++; } %} # runs over all tokens
	: %{ y = sum(x); print x " " y "\n"; Stop; %}
	5652 23020 
	5690 23020 
	5213 23020 
	6465 23020 
	: 
which will of course fail if the variable holds anything other than integer values.An example that illustrates the use of both the sum and the a_unify function is the following Cobra program that can be used to find the ten most frequently occuring trigrams of token types in the input. 
	ncore 4	# use four cores in paralel
	%{
		q = .nxt;
		r = q.nxt;
		if (.typ != "" && q.typ != "" && r.typ != "")
		{	Trigram[.typ, q.typ, r.typ]++;
		}
	%}
	track start _tmp_
	%{
		if (cpu != 0)
		{	Stop;
		}
		a_unify(0);
		for (i in Trigram)
		{	print i "\t" sum(Trigram[i]) "\n";
		}
		Stop;
	%}
	track stop
	!sort -k2 -n < _tmp_ | tail -10; rm -f _tmp_
At the time of writing, applying this Cobra program to the Cobra sources produces the following output:$ cobra -f play/trigram *.[ch] ident,oper,chr 209 const_int,oper,ident 231 oper,oper,ident 232 storage,type,oper 239 key,const_int,oper 250 storage,type,ident 702 ident,oper,const_int 1000 type,oper,ident 1298 oper,ident,oper 3541 ident,oper,ident 5695The processing uses two inline program fragments. The first collects the data, which is assumed to be performed in a multi-core run. It will also work correctly if only a single cpu is used of course, but in that case we could use a simpler version of the code as well. Before the second program fragment is executed we divert the output to a file called _tmp_ with a track command. We conclude the diversion after the fragment has executed, and then use a regular extern sort and tail command to process the data, after which the temporary tracking file is deleted. The second program fragment is used to process the data that was collected by the multiple cpus before -- each cpu working on a different portion of the input sequence. The cores store the data they collect in private copies of the associative array, to avoid race conditions or the chance of data corruption with simultaneous access to the same fields of the array. This means that we must unify the contents of the associative array to make all elements visible to a given cpu. We select the cpu numbered 0 for this, and halt all other cpus at the start of the second fragment. Cpu 0 then calls a_unify, passing it its cpu number 0. Once this call completes, we cpu 0 has access to all the array indices of Trigram, no matter which core added that array index. Importantly though, the values stored at the indices are not collected. Note that the value stored could be a token reference, a string, or a number, so it isn't possible to define a uniform way to unify all that data. In this case, we know the array elements are used to store integer counts, and we want to add up all those counts. That is done with the call to the predefined function sum. | ||||
| Inline Programs Manual Tutorial | (Last Updated: 10 January 2025) | |||