-1

I have a problem with trying to get the job done under 4 seconds using either parallel or task schedule or whatever. The script is simple, it will try to process 100 times all at once and wait for the result. My pc is E5-2603 Xeon processor(4 core, 4 thread, 1.80GHz) with 16GB and it took forever to process 100 hundred tasks.

What can I do to speed up the process?

Test script

static void Main(string[] args)
        {
            //Monitor performance
            Stopwatch totalTime = new Stopwatch();


            //Test case # 1
            totalTime.Start();
            TaskTechnique();
            totalTime.Stop();
            Console.WriteLine("TaskTechnique total time: " + totalTime.Elapsed.TotalSeconds);
            Console.WriteLine("--------------------------------------");

            //Test case #2
            totalTime.Reset();
            totalTime.Start();
            ParalelelTechnique();
            totalTime.Stop();
            Console.WriteLine("ParalelelTechnique total time: " + totalTime.Elapsed.TotalSeconds);
            Console.WriteLine("--------------------------------------");

        }

When Task Class Definition Namespace: System.Threading.Tasks

It tooks 21 seconds to process 100 little tasks. The code is below

    /// <summary>
    /// Using task to process all tasks
    /// </summary>
    private static void TaskTechnique()
    {

        //Test case 1 process all 100 tasks at once
        List<Task<string>> tasks = new List<Task<string>>();
        for (int i = 0; i < 100; i++)
        {
            tasks.Add(
                    Task.Factory.StartNew
                    (
                            () =>
                            {
                                return MrDelay(i);
                            }
                    )
                );
        }

        Task.WaitAll(tasks.ToArray());//Wait until finished 
    }

And when I used Parallel, it took 33 seconds to process.

  /// <summary>
        /// Using Paralelel to process 100 times
        /// </summary>
        private static void ParalelelTechnique()
        {
            //Monitor performance
            Stopwatch startTime = new Stopwatch();
            //Test case 2 using parallel
            startTime.Restart();
            int maxTask = 100;
            var result = Parallel.For
                (
                1, 101,
                new ParallelOptions { MaxDegreeOfParallelism = maxTask },
                (i, state) =>
                {
                    Console.WriteLine("Beginning iteration {0} at ", i, DateTime.Now.ToLocalTime());
                    string longTaskResult = MrDelay(i);
                    Console.WriteLine("Completed iteration {0} result at : {1}", i, longTaskResult);
                }
                );
            startTime.Stop();
            Console.WriteLine("test case 2: " + startTime.Elapsed.TotalSeconds);
        }

Delay process code.

/// <summary>
    /// Just to slow down the test to 3 seconds before return the data back
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private static string MrDelay(int id)
    {
        //Delay 3 seconds then return the result
        System.Threading.Thread.Sleep(3000);
        return "ok request id: " + id.ToString() + "  processed time: " + DateTime.Now.ToLocalTime();
    }
NanoHead
  • 159
  • 3
  • 15
  • 1
    Both `StartNew` and `Parallel.For` are specifically designed for *CPU bound* work. If you don't actually have CPU bound work to do (which you don't, in your example), they won't work effectively. If you want to create a task that will complete in 3 seconds use `Task.Delay`. – Servy Apr 04 '18 at 20:34
  • 6
    If you want to speed it up you can remove all the `Thread.Sleeps`! You're putting workers to sleep and then wondering why they're not working! – Eric Lippert Apr 04 '18 at 20:37
  • 5
    Also, do some math. You have 100 tasks. Each task takes 3 seconds of worker time. You have four cores, so four workers can be running at once. So that's 25 tasks per core, and so it should take about 75 seconds. You're getting the job done in 21, so why are you complaining? – Eric Lippert Apr 04 '18 at 20:41
  • 3
    The takeaway here is, as Servy said: if you're doing CPU bound work then **your test needs to actually stress a CPU, not sleep**, so that the task parallel library can schedule cores effectively. If you're doing IO bound work then **your test should be keeping everything on one thread and let the compiler worry about the control flow by putting awaits in the right places**. You've made a sample program that does everything exactly wrong and so it should not be surprising that you get garbage results. – Eric Lippert Apr 04 '18 at 20:46
  • I've a feeling that what you've posted isn't what you're actually trying to do. You're not actually processing anything just `Thread.Sleep`ing – phuzi Apr 04 '18 at 20:46
  • The Thread.Sleep code is just for show the example of the delay, this code will be replaced with download data or processing files. I think i may mislead you all. – NanoHead Apr 04 '18 at 20:49
  • @NanoHead No, not really, all of the comments still apply. Again, your actual work is not CPU bound work, and yet you're using tools specifically designed to handle CPU bound work. – Servy Apr 04 '18 at 20:57
  • 2
    Right, if you are mostly downloading data then parallelizing is not going to help anything; it's going to make it worse. **Does hiring one person for each letter you get in the mail make your mail come faster**? Of course not. You only need *one* person to sort mail *as it arrives*. But if you have ten thousand tax returns to prepare, then hiring four workers *does* get the job done four times faster than doing them all yourself. **Only parallelize work if it is saturating a core**. Never parallelize work that is waiting for an IO event. – Eric Lippert Apr 04 '18 at 21:07
  • 1
    It would probably help if you had a solid understanding of what the TPL is doing. As others have noted, the TPL is built on the assumption that (1) workers (threads) are expensive, (2) the work will stress a CPU for a long time. In your scenario the work is stressing the CPU for no time, and so the TPL is assuming, oh, that task must have been IO bound, let me schedule another worker, oh, there are no more workers in the pool; **do I want to take on the expense of hiring another worker**? Threads are **insanely expensive**; you **never** want to hire hundreds of them to sleep! – Eric Lippert Apr 04 '18 at 21:17
  • 1
    You were probably expecting the parallel version to take zero time, but the TPL is working very hard to try to keep from hiring 100 workers and consuming 100 megabytes of thread stacks. Now, by contrast, if you make 100 `Task.Delay()` tasks in an array and did a `WhenAll` on them, then they all would run completely "in parallel" because *no actual work is being done* and there would be only *one thread*. If I gave you, personally, 100 tasks, and those tasks were all "wait three seconds before you have lunch", then you too could do all 100 of those tasks in the same 3 seconds. – Eric Lippert Apr 04 '18 at 21:20
  • So anyone has any idea on how to run 100 tasks at the same time, it has to be done within 4 seconds. My cpu is doing nothing but wait. I'm looking into the async and await options now. – NanoHead Apr 04 '18 at 21:21
  • 1
    `Task[] tasks = { Task.Delay(3000), Task.Delay(3000), .... }; Task t = Task.WhenAll(tasks); await t;`, done. – Eric Lippert Apr 04 '18 at 21:23
  • He should have been directed earlier toward an I/O bound solution like [aysnc/await](https://stackoverflow.com/q/14896856/538763) and [concurrent-asynchronous-io (blog)](http://www.tugberkugurlu.com/archive/how-and-where-concurrent-asynchronous-io-with-asp-net-web-api) – crokusek Apr 04 '18 at 23:04

2 Answers2

0

For mass-service code, you have to answer the question first: what should be maxed?

For example, using Tasks intends "maximal throughput". That means your tasks can be interrupted frequently awaiting for something, so let CPU core switch to another task to not waste the time. Average execution time per task is relatively long but average amount of tasks processed per given time is sky-rocketing.

Another case here is "maximal CPU burn". That staff is, for instance, like processing audio/video streams, big math mill like cryptography etc. That way, there is most effective way is to have one thread/task per spare CPU core, not more (because switching to another thread would cost you extra CPU efforts). Opposite to previous example, here we have relatively low average amount of tasks processed per given time. But average execution time is as short as possible due minimal interruption.

Sure thing, in real practice you have something in between of that maximas. Sure thing, you have to know much more about what can pin down your CPU (kernel calls, cache pollution, false sharing, data races and many more). You can start with the great article from "No Bugs Hare": http://ithare.com/using-parallel-algorithm-without-a-clue-90x-performance-loss-instead-of-8x-gain/

Yury Schkatula
  • 5,291
  • 2
  • 18
  • 42
-3

Thanks for the hints, I came up with another solution by using multithreading. This application will be used on the desktop application so this is acceptable. I was able to process 200 tasks and more for under 3 seconds using thread instead of 30+ seconds for 100 task.

  static void Main(string[] args)
    {
        #region MyRegion
        for (int i = 0; i < 200; i++)
        {
            //Create thread
            Thread thread = new Thread(() => DoBigJobThread(i));
            thread.Start();
        }
        #endregion
        //Slow  down to show screen
        System.Threading.Thread.Sleep(3000000);
    }

    static void DoBigJobThread(int id)
    {
        Console.WriteLine(MrDelaySimple(id));
    }

    private static string MrDelaySimple(int id)
    {
        //Delay 3 seconds
        System.Threading.Thread.Sleep(3000);
        return "ok request id: " + id.ToString() + "  processed time: " + DateTime.Now.ToLocalTime();
    }
NanoHead
  • 159
  • 3
  • 15
  • 1
    NEVER EVER DO THIS. Read the comments again; this is exactly the wrong way to solve this problem. The correct solution for IO is to keep all the work on *one thread* and use asynchronous workflows to coordinate the tasks. The correct solution for CPU work is *as many threads as there are processors, not more*. If you are making threads to do IO work, or hiring more threads than there are CPUs to service them, you are doing something very, very wrong. – Eric Lippert Apr 07 '18 at 14:43
  • 1
    This solution is basically "I have 200 letters to send, so I'm going to hire 200 secretaries". It's incredibly wasteful and it does not scale. – Eric Lippert Apr 07 '18 at 14:45
  • Read the sentence of the previous comment that begins "The correct solution for IO is..." if you want to know what the correct way to handle it is. – Eric Lippert May 14 '18 at 17:02