You are on page 1of 12

1

1 Introduction to Problem Statement


Binary Search is a divide-and-conquer searching algorithm which searches for an input value in a sorted array. The average-
case time complexity for Binary Search is O(logn). Considering that it is a divide-and-conquer search where the input array
is repeatedly divided into equal halves, can the search operation be made even faster using OpenMP parallelization?

Prior Solutions
Parallel Binary Search implementations with OpenMP do exist, however; rarely is there any specifically real-world timing
data comparison between a serial and parallel implementation. Due to such circumstance, this scientific paper aims to
compare the real-world performance between a parallel and serial implementation. It also aims to investigate any real-world
performance differences between a serial and parallel approach to Binary Search.

2 Design
Typical Serial Binary Search
Typically, a serial approach to non-recursive Binary Search algorithm goes as follows:

Listing 1. Serial Binary Search


while ( f i r s t <= l a s t ) {
i n t middle = f i r s t + ( l a s t − f i r s t ) / 2 ;

/ / Check i f s earch value i s p r e s e n t a t middle


i f ( randomNums [ middle ] == s e a r c h V a l )
return middle ;

/ / I f s earch value g r eater , i gnore l e f t h a l f


i f ( randomNums [ middle ] < s e a r c h V a l ) { f i r
s t = middle + 1 ;
}

/ / I f s earch value i s smaller , i gnore r i g h t h a l f


else
} l a s t = middle − 1 ;
Parallel Binary Search Design
The sample code block below may assist with understanding the parallel solution.

Listing 2. OpenMP Binary Search


# pragma omp p a r a l l e l num_threads ( num_threads )
{
# pragma omp s e c t i o n s
{
/ Function
∗ parameters :
binary Search_openmp ( f i r s t _ i n d e x , l a s t _ i n d e x , s e a r ch _ va l u e ) ;
∗/

# pragma omp s e c t i o n
t h r e a d _ o n e = binary Search_openmp ( 0 , q u a r t e r _ s l i c e , s e a r c h V a l ) ;
# pragma omp s e c t i o n
t h r e a d _ t w o = binary Search_openmp ( q u a r t e r _ s l i c e + 1 , middle , s e a r c h V a l ) ;
# pragma omp s e c t i o n
t h r e a d _ t h r e e = binary Search_openmp ( middle + 1
# pragma , quarter_slice

3 , s e a r c h V a l ) ; omp s e c t i o n
} t h r e a d _ f o u r = binary Search_openmp ( ( q u a r t e r _ s l i c e ∗ 3 ) + 1 , l a s t , s e a r c h V a l ) ;
}

Parallel Algorithm Explanation


Each call to the function responsible for binary search is now given the input of a new array size, where each input array is a
quarter size of the original input array. From there, each thread will simultaneously perform a search operation on a quarter
section of the array. Once a single thread finds the desired value to search for, all threads will stop the searching process and
return the index of the search value.
The pragma directive of choice here is using sections, so that each task can run on its own thread. The idea behind this
algorithm is to reduce the search array size and reduce the number of operations, while each thread can perform each
operation in parallel.
Data Sources
All data was generated using an integer generator. All numbers are placed in ascending order into a file which will be used
by the operation code. The data is inserted into an array in the source code to perform Binary Search experimentation upon.

Data Type Integer


Size 200,000,000
Memory Type fixed-size array
Order Ascending
Sorted Yes

Table 1. Data Source Information

Results
Below, contains both timing results between Parallel Binary Search and Parallel Binary Search using OpenMP. Timing data
was recorded using omp_get_wtime(). Four runs were taken per entry and timing data was averaged. The operation was a

Binary Search, where the user will input any number between 1 and 200,000,00 to search for.
Thread Run time (seconds)
Count

1 0.0000061

2 0.0000269

4 0.0000421

8 0.0000564

Thread Count time (seconds)


1 (Serial) ∼0.000005

Table 2. Serial Binary Search Timing Table 3. Parallel Binary Search Timing
Comparison

Parallel Time Complexity O(log(n/k))


Speedup Estimate log(n
2)

k
Efficiency Estimate log(n
2)
k2
Table 4. Theoretical Estimates of Parallel Binary Search

Real-World Results
Referring to the data displayed in Table 2 and Table 3, the serial implementation of Binary Search is significantly faster than
the parallel implementation. As the number of threads performing Binary Search increased, the operation took exponentially
longer. There are a few reasons I can hypothesize as to why these results occurred.
Effectivity Limits of Parallel Solutions
One crucial idea to note, when finding a parallel approach to a problem, is that the serial operation needs to be costly
enough to computer resources to parallelize. Often times, complex calculations such as matrix multiplication or dot product
are used to demonstrate parallel effectivity, for a specific reason. That is, the operation needs to be costly enough to
outweigh the performance repercussions of a parallel solution. Specific performance repercussions such as thread spawning,
excessive function calls, voltage and thermal limitations across several threads, and cache hierarchy issues can all play a
part in slowing down performance. In the case of Binary Search, the operation simply is not costly enough to make useful
out of a parallel approach. OpenMP is not a solution to all problems.
Implementation

Listing 3. binarySearch_openMP.c
#include"stdio.h"
#include"stdlib.h
"#include"string.
h"
# i n c l u d e < s y s / t im e . h> / / High r e s o l u t i o n t i m e r
# i n c l u d e <omp . h>

c o n s t i n t ARR_SIZE = 200000000 ; / / Add 200 , 000 , 000 e l e m e n t s i n t o array


i n t randomNums [ ARR_SIZE ] ; / / Array o f random i n t e g e r s
intparallel_search_found=0;
double omp_get_wtime ( void ) ;

/ / High r e s o l u t i o n t i m e r
inlineuint64_t rdtsc()
{
u i n t 3 2 _ t lo , h i ;
__asm volatile__(
" x o r l %%eax , %%e ax \ n "
"cpuid\n"
"rdtsc\n"
: " =a " ( l o ) , " =d " ( h i )
:
: "%ebx " , "%ecx " ) ;
return ( u i n t 6 4 _ t ) h i << 32 | l o ;
}

/ ∗
void r e a d I n p u t F i l e ( ) −
Takes c o n t e n t s from f i l e i n p u t 1 . t x t and p l a c e s i t i n t o array randomNums [ ]
∗/
void r e a d I n p u t F i l e ( )
{
FILE i n∗p u t F i l e ;
i n p u t F i l e = fopen ( " i n p u t 1 . t x t " , " r " ) ;

inti;

i f ( i n p u t F i l e == NULL)
{
p r i n t f ( " E r r o r Reading F i l e i n p u t 1 . t x t \ n " ) ; e
xit(0);
}

p r i n t f ( " P o p u l a t i n g randomNums [ ] wi t h d a t a from i n p u t 1 . t x t . . . \ n " ) ; p r


i n t f ( " ( Thi s may t a k e a w h i l e ) \ n " ) ;
/ / Place f i l e c o n t e n t s i n t o array randomNums [ ]
f o r ( i = 0 ; i < ARR_SIZE ; i ++)
{
f s c a n f ( i n p u t F i l e , "%d , " , &randomNums [ i ] ) ;
}

/ / / / P r i n t a few v a l u e s from array t o v e r i f y e l e m e n t s


/ / f o r ( i n t i = 0 ; i < 4 ; i ++) {
// p r i n t f ("% d \ n " , randomNums [ i ] ) ;
//}

p r i n t f ( " F i n i s h e d i n s e r t i n g %d e l e m e n t s i n t o randomNums [ ] \ n " , ARR_SIZE ) ; f

close(inputFile);

printf("\n");
}

/ ∗
void b i n a r y S e a r c h _ s e r i a l ( ) −
Performs s e r i a l b i nary s earch on randomNums [ ]
∗/
intbinarySearch_serial(int searchVal)
{

i n t num_elements = ARR_SIZE ;
intfirst=0;
i n t l a s t = num_elements − 1 ;

while ( f i r s t <= l a s t )
{
i n t middle = f i r s t + ( l a s t − f i r s t ) / 2 ;

/ / Check i f s earch value i s p r e s e n t a t mid


i f ( randomNums [ middle ] == s e a r c h V a l )
return middle ;

/ / I f s earch value g r eater , i gnore l e f t h a l f


i f ( randomNums [ middle ] < s e a r c h V a l )
{
f i r s t = middle + 1 ;
}

/ / I f s earch value i s smaller , i gnore r i g h t h a l f


else
} l a s t = middle − 1 ;

/ / i f we reach here , t hen e l ement was


/ / not p r e s e n t
} return −1;

/ ∗
void binary Search_openmp ( ) −
Performs p a r a l l e l b i nary s earch u s i ng OpenMP on randomNums [ ]
∗/
i n t binary Search_openmp ( i n t f i r s t , i n t l a s t , i n t s e a r c h V a l )
{
/ / / / P r i n t c u r r e n t t h r ead number
/ / i n t t i d = omp_get_thread_num ( ) ;
/ / p r i n t f ( " Hello World from t h r ead = %d \ n " , t i d ) ;
while ( f i r s t <= l a s t )
{
i n t middle = f i r s t + ( l a s t − f i r s t ) / 2 ;

/ / Check i f s earch value i s p r e s e n t a t mid


i f ( randomNums [ middle ] == s e a r c h V a l )
return middle ;

/ / I f s earch value g r eater , i gnore l e f t h a l f


i f ( randomNums [ middle ] < s e a r c h V a l )
{
f i r s t = middle + 1 ;
}

/ / I f s earch value i s smaller , i gnore r i g h t h a l f


else
} l a s t = middle − 1 ;

/ / i f we reach here , t hen e l ement was


/ / not p r e s e n t
} return −1;

/ ∗
void s e r i a l _ w o r k ( ) −
Performs a l l work r e l a t e d t o S e r i a l code , i n c l u d i n g :
— Allprint statements
— Timing o f S e r i a l work
— Binary s earch f u n c t i o n c a l l s
∗/
void s e r i a l _ w o r k ( i n t s e a r c h V a l )
{
intresult;
double s t a r t , end , t o t a l _ t i m e ;

p r i n t f ( " \ n ∗∗∗∗∗∗ Now b e g i n n i n g S e r i a l work ∗∗∗∗∗∗\ n \ n " )


;printf("Startingbinarysearch...\n");

s t a r t = omp_get_wtime ( ) ;
/ / Perform s e r i a l Binary s earch with t i m i n g
r e s u l t = b i n a r y S e a r c h _ s e r i a l ( s e a r c h V a l ) ; / / Binary s earch here

end = omp_get_wtime ( ) ;
t o t a l _ t i m e = end − s t a r t ;

p r i n t f ( " Work t o ok %f s e c o n d s \ n " , t o t a l _ t i m e ) ;

/ / P r i n t r e s u l t s o f s e r i a l Binary s earch
if (result!= 1)−
{
p r i n t f ( " Element %d found ! At i n d e x %d \ n " , s e a r c h V a l , r e s u l t ) ;
}
else
{
p r i n t f ( " Element %d n o t found \ n " , s e a r c h V a l ) ;
}
printf("\n");
}

/ ∗
void p a r a l l e l _ w o r k ( ) −
Performs a l l work r e l a t e d t o P a r a l l e l i z e d code , i n c l u d i n g :
— Allprint statements
— Timing o f p a r a l l e l work
— Binary s earch f u n c t i o n c a l l s
∗/
void p a r a l l e l _ w o r k ( i n t s e a r c h V a l , i n t num_threads )
{
intresult;
double s t a r t , end , t o t a l _ t i m e ;

/ / For use with P a r a l l e l s earch


intfirst=0;
i n t l a s t = ARR_SIZE − 1 ;
i n t middle = f i r s t + ( l a s t − f i r s t ) / 2 ;

/ / Array w i l l be s l i c e d i n t o s e c t i o n s
intthread_one, thread_two, thread_three, thread_four;
i n t q u a r t e r _ s l i c e = middle / 2 ;

p r i n t f ( " \ n ∗∗∗∗∗∗ Now b e g i n n i n g P a r a l l e l work wi t h OpenMP ∗∗∗∗∗∗\ n \ n " ) ;


printf("Startingbinarysearch...\n");

s t a r t = omp_get_wtime ( ) ;

# pragma omp p a r a l l e l num_threads ( num_threads )


{
# pragma omp s e c t i o n s
{
/ Function
∗ parameters :
binary Search_openmp ( f i r s t _ i n d e x , l a s t _ i n d e x , s ea r ch _ va l u e ) ;
∗/

# pragma omp s e c t i o n
t h r e a d _ o n e = binary Search_openmp ( 0 , q u a r t e r _ s l i c e , s e a r c h V a l ) ;
# pragma omp s e c t i o n
t h r e a d _ t w o = binary Search_openmp ( q u a r t e r _ s l i c e + 1 , middle , s e a r c h V a l ) ;
# pragma omp s e c t i o n
t h r e a d _ t h r e e = binary Search_openmp ( middle + 1
# pragma , quarter_slice

3 , s e a r c h V a l ) ; omp s e c t i o n
} t h r e a d _ f o u r = binary Search_openmp ( ( q u a r t e r _ s l i c e ∗ 3 ) + 1 , l a s t , s e a r c h V a l ) ;
}

end = omp_get_wtime ( ) ;
t o t a l _ t i m e = end − s t a r t ;
p r i n t f ( " Work t o ok %f s e c o n d s \ n " , t o t a l _ t i m e ) ;

/ / P r i n t r e s u l t s o f s e r i a l Binary s earch
if (result!= 1)−
{
p r i n t f ( " Element %d found ! At i n d e x %d \ n " , s e a r c h
} Val, result);
else
{

} p r i n t f ( " Element %d n o t found \ n " , s e a r c h V a l ) ;


printf("\n");
}

i n t main ( i n t argc , char a rg


∗v[])
{
i n t s e a r c h V a l , num_threads ;

printf("Entervaluetosearchfor: \n");s
c a n f ( "%d " , &s e a r c h V a l ) ;

p r i n t f ( "How many t h r e a d s t o run on : \ n " ) ; s c a


n f ( "%d " , &num_threads ) ;

/ / Read c o n t e n t s o f i n p u t f i l e and p o p u l a t e array


readInputFile();

/ / Perform a l l s e r i a l work
serial_work(searchVal);

/ / Rewrite c o n t e n t s t o array f o r c o n s i s t e n c y purposes


readInputFile();

/ / Perform a l l p a r a l l e l i z e d work
p a r a l l e l _ w o r k ( s e a r c h V a l , num_threads ) ;

return 0 ;
}
Listing 4. integer_generator.py
’’’
Generates random i n t e g e r s and p l a c e s a l l data i n t o i n p u t 1 . t x t Each i n t

e g e r w i l l be w r i t t i n i n t o a new l i n e i n i n p u t 1 . t x t

’’’
import random

n u m s _ t o _ g e n e r a t e = 200000000
lower_bound = 0
upper_bound = n u m s _ t o _ g e n e r a t e

o u t p u t _ f i l e = open ( " i n p u t 1 . t x t " , "w" )

p r i n t ( " G e n e r a t i n g " , n u m s _ t o _ g e n e r a t e , " random i n t e g e r s . . . " )


p r i n t ( " ( Th i s may t a k e a w h i l e ) " )

f o r i in range ( n u m s _ t o _ g e n e r a t
e):line=str(i)
output_file.write(line)
output_file.write(’\n’)

p r i n t ( " Random numbers g e n e r a t e d : " , n u m s _ t o _ g e n e r a t e )


p r i n t ( " G e n e r a t e d numbers r a n g e from : " , lower_bound , " t o " , upper_bound )
p r i n t ( " Random number g e n e r a t i o n completed . \ n " )

output_file.close()

You might also like