Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static method vs instance method, multi threading, performance

Can you help explain how multiple threads access static methods? Are multiple threads able to access the static method concurrently?

To me it would seem logical that if a method is static that would make it a single resouce that is shared by all the threads. Therefore only one thread would be able to use it at a time. I have created a console app to test this. But from the results of my test it would appear that my assumption is incorrect.

In my test a number of Worker objects are constructed. Each Worker has a number of passwords and keys. Each Worker has an instance method that hashes it's passwords with it's keys. There is also a static method which has exactly the same implementation, the only difference being that it is static. After all the Worker objects have been created the start time is written to the console. Then a DoInstanceWork event is raised and all of the Worker objects queue their useInstanceMethod to the threadpool. When all the methods or all the Worker objects have completed the time it took for them all to complete is calculated from the start time and is written to the console. Then the start time is set to the current time and the DoStaticWork event is raised. This time all the Worker objects queue their useStaticMethod to the threadpool. And when all these method calls have completed the time it took until they had all completed is again calculated and written to the console.

I was expecting the time taken when the objects use their instance method to be 1/8 of the time taken when they use the static method. 1/8 because my machine has 4 cores and 8 virtual threads. But it wasn't. In fact the time taken when using the static method was actually fractionally faster.

How is this so? What is happening under the hood? Does each thread get it's own copy of the static method?

Here is the Console app-

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Threading;

namespace bottleneckTest
{
    public delegate void workDelegate();

    class Program
    {
        static int num = 1024;
        public static DateTime start;
        static int complete = 0;
        public static event workDelegate DoInstanceWork;
        public static event workDelegate DoStaticWork;
        static bool flag = false;

        static void Main(string[] args)
        {
            List<Worker> workers = new List<Worker>();
            for( int i = 0; i < num; i++){
                workers.Add(new Worker(i, num));
            }
            start = DateTime.UtcNow;
            Console.WriteLine(start.ToString());
            DoInstanceWork();
            Console.ReadLine();
        }
        public static void Timer()
        {
            complete++;
            if (complete == num)
            {
                TimeSpan duration = DateTime.UtcNow - Program.start;
                Console.WriteLine("Duration:  {0}", duration.ToString());
                complete = 0;
                if (!flag)
                {
                    flag = true;
                    Program.start = DateTime.UtcNow;
                    DoStaticWork();
                }
            }
        }
    }
    public class Worker
    {
        int _id;
        int _num;
        KeyedHashAlgorithm hashAlgorithm;
        int keyLength;
        Random random;
        List<byte[]> _passwords;
        List<byte[]> _keys;
        List<byte[]> hashes;

        public Worker(int id, int num)
        {
            this._id = id;
            this._num = num;
            hashAlgorithm = KeyedHashAlgorithm.Create("HMACSHA256");
            keyLength = hashAlgorithm.Key.Length;
            random = new Random();
            _passwords = new List<byte[]>();
            _keys = new List<byte[]>();
            hashes = new List<byte[]>();
            for (int i = 0; i < num; i++)
            {
                byte[] key = new byte[keyLength];
                new RNGCryptoServiceProvider().GetBytes(key);
                _keys.Add(key);

                int passwordLength = random.Next(8, 20);
                byte[] password = new byte[passwordLength * 2];
                random.NextBytes(password);
                _passwords.Add(password);
            }
            Program.DoInstanceWork += new workDelegate(doInstanceWork);
            Program.DoStaticWork += new workDelegate(doStaticWork);
        } 
        public void doInstanceWork()
        {
            ThreadPool.QueueUserWorkItem(useInstanceMethod, new WorkerArgs() { num = _num, keys = _keys, passwords = _passwords });
        }
        public void doStaticWork()
        {
            ThreadPool.QueueUserWorkItem(useStaticMethod, new WorkerArgs() { num = _num, keys = _keys, passwords = _passwords });
        }
        public void useInstanceMethod(object args)
        {
            WorkerArgs workerArgs = (WorkerArgs)args;
            for (int i = 0; i < workerArgs.num; i++)
            {
                KeyedHashAlgorithm hashAlgorithm = KeyedHashAlgorithm.Create("HMACSHA256");
                hashAlgorithm.Key = workerArgs.keys[i];
                byte[] hash = hashAlgorithm.ComputeHash(workerArgs.passwords[i]);
            }
            Program.Timer();
        }
        public static void useStaticMethod(object args)
        {
            WorkerArgs workerArgs = (WorkerArgs)args;
            for (int i = 0; i < workerArgs.num; i++)
            {
                KeyedHashAlgorithm hashAlgorithm = KeyedHashAlgorithm.Create("HMACSHA256");
                hashAlgorithm.Key = workerArgs.keys[i];
                byte[] hash = hashAlgorithm.ComputeHash(workerArgs.passwords[i]);
            }
            Program.Timer();
        }
        public class WorkerArgs
        {
            public int num;
            public List<byte[]> passwords;
            public List<byte[]> keys;
        }
    }
}
like image 748
Duncan Gravill Avatar asked Mar 11 '12 14:03

Duncan Gravill


3 Answers

Methods are code - there's no problem with thread accessing that code concurrently since the code isn't modified by running it; it's a read-only resource (jitter aside). What needs to be handled carefully in multi-threaded situations is access to data concurrently (and more specifically, when modifying that data is a possibility). Whether a method is static or an instance method has nothing to do with whether or not it needs to ne serialized in some way to make it threadsafe.

like image 182
Michael Burr Avatar answered Nov 15 '22 04:11

Michael Burr


In all cases, whether static or instance, any thread can access any method at any time unless you do explicit work to prevent it.

For example, you can create a lock to ensure only a single thread can access a given method, but C# will not do that for you.

Think of it like watching TV. A TV does nothing to prevent multiple people from watching it at the same time, and as long as everybody watching it wants to see the same show, there's no problem. You certainly wouldn't want a TV to only allow one person to watch it at once just because multiple people might want to watch different shows, right? So if people want to watch different shows, they need some sort of mechanism external to the TV itself (perhaps having a single remote control that the current viewer holds onto for the duration of his show) to make sure that one guy doesn't change the channel to his show while another guy is watching.

like image 21
Gabe Avatar answered Nov 15 '22 03:11

Gabe


C# methods are "reentrant" (As in most languages; the last time I heard of genuinely non-reentrant code was DOS routines) Each thread has its own call stack, and when a method is called, the call stack of that thread is updated to have space for the return address, calling parameters, return value, local values, etc.

Suppose Thread1 and Thread2 calls the method M concurrently and M has a local int variable n. The call stack of Thread1 is seperate from the call stack of Thread2, so n will have two different instantiations in two different stacks. Concurrency would be a problem only if n is stored not in a stack but say in the same register (i.e. in a shared resource) CLR (or is it Windows?) is careful not to let that cause a problem and cleans, stores and restores the registers when switching threads. (What do you do in presence of multiple CPU's, how do you allocate registers, how do you implement locking. These are indeed difficult problems that makes one respect compiler, OS writers when one comes to think of it)

Being reentrant does not prove no bad things happen when two threads call the same method at the same time: it only proves no bad things happen if the method does not access and update other shared resources.

like image 31
Ali Ferhat Avatar answered Nov 15 '22 03:11

Ali Ferhat