Viewed   124 times

What's the performance difference (if there is any) between these three approaches, both used to transform an array to another array?

  1. Using foreach
  2. Using array_map with lambda/closure function
  3. Using array_map with 'static' function/method
  4. Is there any other approach?

To make myself clear, let's have look at the examples, all doing the same - multiplying the array of numbers by 10:

$numbers = range(0, 1000);

Foreach

$result = array();
foreach ($numbers as $number) {
    $result[] = $number * 10;
}
return $result;

Map with lambda

return array_map(function($number) {
    return $number * 10;
}, $numbers);

Map with 'static' function, passed as string reference

function tenTimes($number) {
    return $number * 10;
}
return array_map('tenTimes', $numbers);

Is there any other approach? I will be happy to hear actually all differences between the cases from above, and any inputs why one should be used instead of others.

 Answers

1

FWIW, I just did the benchmark since poster didn't do it. Running on PHP 5.3.10 + XDebug.

UPDATE 2015-01-22 compare with mcfedr's answer below for additional results without XDebug and a more recent PHP version.


function lap($func) {
  $t0 = microtime(1);
  $numbers = range(0, 1000000);
  $ret = $func($numbers);
  $t1 = microtime(1);
  return array($t1 - $t0, $ret);
}

function useForeach($numbers)  {
  $result = array();
  foreach ($numbers as $number) {
      $result[] = $number * 10;
  }
  return $result;
}

function useMapClosure($numbers) {
  return array_map(function($number) {
      return $number * 10;
  }, $numbers);
}

function _tenTimes($number) {
    return $number * 10;
}

function useMapNamed($numbers) {
  return array_map('_tenTimes', $numbers);
}

foreach (array('Foreach', 'MapClosure', 'MapNamed') as $callback) {
  list($delay,) = lap("use$callback");
  echo "$callback: $delayn";
}

I get pretty consistent results with 1M numbers across a dozen attempts:

  • Foreach: 0.7 sec
  • Map on closure: 3.4 sec
  • Map on function name: 1.2 sec.

Supposing the lackluster speed of the map on closure was caused by the closure possibly being evaluated each time, I also tested like this:


function useMapClosure($numbers) {
  $closure = function($number) {
    return $number * 10;
  };

  return array_map($closure, $numbers);
}

But the results are identical, confirming that the closure is only evaluated once.

2014-02-02 UPDATE: opcodes dump

Here are the opcode dumps for the three callbacks. First useForeach():



compiled vars:  !0 = $numbers, !1 = $result, !2 = $number
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
  10     0  >   EXT_NOP                                                  
         1      RECV                                                     1
  11     2      EXT_STMT                                                 
         3      INIT_ARRAY                                       ~0      
         4      ASSIGN                                                   !1, ~0
  12     5      EXT_STMT                                                 
         6    > FE_RESET                                         $2      !0, ->15
         7  > > FE_FETCH                                         $3      $2, ->15
         8  >   OP_DATA                                                  
         9      ASSIGN                                                   !2, $3
  13    10      EXT_STMT                                                 
        11      MUL                                              ~6      !2, 10
        12      ASSIGN_DIM                                               !1
        13      OP_DATA                                                  ~6, $7
  14    14    > JMP                                                      ->7
        15  >   SWITCH_FREE                                              $2
  15    16      EXT_STMT                                                 
        17    > RETURN                                                   !1
  16    18*     EXT_STMT                                                 
        19*   > RETURN                                                   null

Then the useMapClosure()


compiled vars:  !0 = $numbers
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
  18     0  >   EXT_NOP                                                  
         1      RECV                                                     1
  19     2      EXT_STMT                                                 
         3      EXT_FCALL_BEGIN                                          
         4      DECLARE_LAMBDA_FUNCTION                                  '%00%7Bclosure%7D%2Ftmp%2Flap.php0x7f7fc1424173'
  21     5      SEND_VAL                                                 ~0
         6      SEND_VAR                                                 !0
         7      DO_FCALL                                      2  $1      'array_map'
         8      EXT_FCALL_END                                            
         9    > RETURN                                                   $1
  22    10*     EXT_STMT                                                 
        11*   > RETURN                                                   null

and the closure it calls:


compiled vars:  !0 = $number
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
  19     0  >   EXT_NOP                                                  
         1      RECV                                                     1
  20     2      EXT_STMT                                                 
         3      MUL                                              ~0      !0, 10
         4    > RETURN                                                   ~0
  21     5*     EXT_STMT                                                 
         6*   > RETURN                                                   null

then the useMapNamed() function:


compiled vars:  !0 = $numbers
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
  28     0  >   EXT_NOP                                                  
         1      RECV                                                     1
  29     2      EXT_STMT                                                 
         3      EXT_FCALL_BEGIN                                          
         4      SEND_VAL                                                 '_tenTimes'
         5      SEND_VAR                                                 !0
         6      DO_FCALL                                      2  $0      'array_map'
         7      EXT_FCALL_END                                            
         8    > RETURN                                                   $0
  30     9*     EXT_STMT                                                 
        10*   > RETURN                                                   null

and the named function it calls, _tenTimes():


compiled vars:  !0 = $number
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
  24     0  >   EXT_NOP                                                  
         1      RECV                                                     1
  25     2      EXT_STMT                                                 
         3      MUL                                              ~0      !0, 10
         4    > RETURN                                                   ~0
  26     5*     EXT_STMT                                                 
         6*   > RETURN                                                   null

Saturday, November 19, 2022
4

If you are using PHP5.5 then you can use the array_column function - documentation

$names = array_column($records, 'Name', 'ID');

Otherwise, an array_map solution is probably as good as you can get:

$names = array_combine(array_map(function($value) {
     return $value['ID'];
}, $records), array_map(function($value) {
     return $value['Name'];
}, $records));

Here's the array_combine docs

Saturday, November 19, 2022
 
geneq
 
1

Something like this should work:

$count = count($data['product']);
for ($i = 0; $i < $count; $i ++) {
    echo $data['product'][$i] . ' ' . $data['date'][$i] . ' ' .$data['time'][$i] . '<br />';
}

To add data-verification/integrity to it (to stop undefined index errors), also check if each index exists in the sub-arrays:

$count = count($data['product']);
for ($i = 0; $i < $count; $i++) {
    $date = isset($data['date'][$i]) ? ' ' . $data['date'][$i] : '';
    $time = isset($data['time '][$i]) ? ' ' . $data['time '][$i] : '';
    echo $data['product'][$i] . $date . $time . '<br />';
}
Sunday, December 11, 2022
 
marco
 
2

I sligthly modified the code of @Serge Semonov and run it on .NET Core 3.1 - it seems the performance of Expression.Compile() has changed dramatically. I have also added code that uses CSharpScript to compile lambdas from string. Note that .CompileToMethod is not available in .NET Core.

Virtual (Func<int>)Expression.Compile(): 908 ms
Direct (Func<int>)Expression.Compile(): 584 ms
Virtual (Func<IFoo, int>)Expression.Compile(): 531 ms
Direct (Func<FooImpl, int>)Expression.Compile(): 426 ms
Virtual (iFooArg) => iFooArg.Bar(): 622 ms
Direct (fooArg) => fooArg.Bar(): 478 ms
Virtual () => IFoo.Bar(): 640 ms
Direct () => FooImpl.Bar(): 477 ms
Virtual IFoo.Bar(): 431 ms
Direct Foo.Bar(): 319 ms
Virtual CSharpScript.EvaluateAsync: 799 ms
Direct CSharpScript.EvaluateAsync: 748 ms
Virtual CSharpScript.EvaluateAsync + Expression.Compile(): 586 ms
Direct CSharpScript.EvaluateAsync + Expression.Compile(): 423 ms
Virtual MethodInfo.Invoke(FooImpl, Bar): 43533 ms
Direct MethodInfo.Invoke(IFoo, Bar): 29012 ms

Code:

#define NET_FW    //if you run this on .NET Framework and not .NET Core or .NET (5+)

using System;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;

namespace ExpressionTest
{
   public interface IFoo
   {
      int Bar();
   }

   public sealed class FooImpl : IFoo
   {
      [MethodImpl(MethodImplOptions.NoInlining)]
      public int Bar()
      {
         return 0;
      }
   }

   class Program
   {
      static void Main(string[] args)
      {
         var foo = new FooImpl();
         var iFoo = (IFoo)foo;

         Func<int> directLambda = () => foo.Bar();
         Func<int> virtualLambda = () => iFoo.Bar();
         Func<FooImpl, int> directArgLambda = fooArg => fooArg.Bar();
         Func<IFoo, int> virtualArgLambda = iFooArg => iFooArg.Bar();
         var compiledDirectCall = CompileBar(foo, asInterfaceCall: false);
         var compiledVirtualCall = CompileBar(foo, asInterfaceCall: true);
         var compiledArgDirectCall = CompileBar<FooImpl>();
         var compiledArgVirtualCall = CompileBar<IFoo>();
         var barMethodInfo = typeof(FooImpl).GetMethod(nameof(FooImpl.Bar));
         var iBarMethodInfo = typeof(IFoo).GetMethod(nameof(IFoo.Bar));
#if NET_FW
         var compiledToModuleDirect = CompileToModule<FooImpl>();
         var compiledToModuleVirtual = CompileToModule<IFoo>();
#endif
         var compiledViaScriptDirect = CompileViaScript<FooImpl>();
         var compiledViaScriptVirtual = CompileViaScript<IFoo>();
         var compiledViaExprScriptDirect = CompileFromExprFromScript<FooImpl>();
         var compiledViaExprScriptVirtual = CompileFromExprFromScript<IFoo>();

         var iterationCount = 0;
         
         int round = 0;
         start:
         if (round == 0)
         {
            iterationCount = 2000000;
            Console.WriteLine($"Burn in");
            Console.WriteLine($"Iteration count: {iterationCount:N0}");
            goto doWork;
         }
         if (round == 1)
         {
            iterationCount = 200000000;
            Console.WriteLine($"Iteration count: {iterationCount:N0}");
            goto doWork;
         }
         return;

         doWork:
         {
            var sw = Stopwatch.StartNew();
            for (int i = 0; i < iterationCount; i++)
               compiledVirtualCall();
            var elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual (Func<int>)Expression.Compile(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledDirectCall();
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct (Func<int>)Expression.Compile(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledArgVirtualCall(iFoo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual (Func<IFoo, int>)Expression.Compile(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledArgDirectCall(foo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct (Func<FooImpl, int>)Expression.Compile(): {elapsedMs} ms");

#if NET_FW
            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledToModuleVirtual(iFoo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual (Func<IFoo, int>)Expression.CompileToMethod(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledToModuleDirect(foo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct (Func<FooImpl, int>)Expression.CompileToMethod(): {elapsedMs} ms");
#endif

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               virtualArgLambda(iFoo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual (iFooArg) => iFooArg.Bar(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               directArgLambda(foo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct (fooArg) => fooArg.Bar(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               virtualLambda();
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual () => IFoo.Bar(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               directLambda();
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct () => FooImpl.Bar(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               iFoo.Bar();
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual IFoo.Bar(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               foo.Bar();
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct Foo.Bar(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledViaScriptVirtual(iFoo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual CSharpScript.EvaluateAsync: {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledViaScriptDirect(foo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct CSharpScript.EvaluateAsync: {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledViaExprScriptVirtual(iFoo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual CSharpScript.EvaluateAsync + Expression.Compile(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
               compiledViaExprScriptDirect(foo);
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct CSharpScript.EvaluateAsync + Expression.Compile(): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
            {
               int result = (int)iBarMethodInfo.Invoke(iFoo, null);
            }
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Virtual MethodInfo.Invoke(FooImpl, Bar): {elapsedMs} ms");

            sw.Restart();
            for (int i = 0; i < iterationCount; i++)
            {
               int result = (int)barMethodInfo.Invoke(foo, null);
            }
            elapsedMs = sw.ElapsedMilliseconds;
            Console.WriteLine($"Direct MethodInfo.Invoke(IFoo, Bar): {elapsedMs} ms");
         }
         round++;
         goto start;
      }

      static Func<int> CompileBar(IFoo foo, bool asInterfaceCall)
      {
         var fooType = asInterfaceCall ? typeof(IFoo) : foo.GetType();
         var methodInfo = fooType.GetMethod(nameof(IFoo.Bar));
         var instance = Expression.Constant(foo, fooType);
         var call = Expression.Call(instance, methodInfo);
         var lambda = Expression.Lambda(call);
         var compiledFunction = (Func<int>)lambda.Compile();
         return compiledFunction;
      }

      static Func<TInput, int> CompileBar<TInput>()
      {
         var fooType = typeof(TInput);
         var methodInfo = fooType.GetMethod(nameof(IFoo.Bar));
         var instance = Expression.Parameter(fooType, "foo");
         var call = Expression.Call(instance, methodInfo);
         var lambda = Expression.Lambda(call, instance);
         var compiledFunction = (Func<TInput, int>)lambda.Compile();
         return compiledFunction;
      }

#if NET_FW
      static Func<TInput, int> CompileToModule<TInput>()
      {
         var fooType = typeof(TInput);
         var methodInfo = fooType.GetMethod(nameof(IFoo.Bar));
         var instance = Expression.Parameter(fooType, "foo");
         var call = Expression.Call(instance, methodInfo);
         var lambda = Expression.Lambda(call, instance);

         var asmName = new AssemblyName(fooType.Name);
         var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
         var moduleBuilder = asmBuilder.DefineDynamicModule(fooType.Name);
         var typeBuilder = moduleBuilder.DefineType(fooType.Name, TypeAttributes.Public);
         var methodBuilder = typeBuilder.DefineMethod(nameof(IFoo.Bar), MethodAttributes.Static, typeof(int), new[] { fooType });
         Expression.Lambda<Action>(lambda).CompileToMethod(methodBuilder);
         var createdType = typeBuilder.CreateType();

         var mi = createdType.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)[1];
         var func = Delegate.CreateDelegate(typeof(Func<TInput, int>), mi);
         return (Func<TInput, int>)func;
      }
#endif

      static Func<TInput, int> CompileViaScript<TInput>()
      {
         ScriptOptions scriptOptions = ScriptOptions.Default;

         //Add reference to mscorlib
         var mscorlib = typeof(System.Object).Assembly;
         var systemCore = typeof(System.Func<>).Assembly;
         var thisAssembly = typeof(IFoo).Assembly;
         scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore, thisAssembly);

         var result = CSharpScript.EvaluateAsync<Func<TInput, int>>("it => it.Bar()", options: scriptOptions).Result;
         return result;
      }
      static Func<TInput, int> CompileFromExprFromScript<TInput>()
      {
         ScriptOptions scriptOptions = ScriptOptions.Default;

         //Add reference to mscorlib
         var mscorlib = typeof(System.Object).Assembly;
         var systemCore = typeof(System.Func<>).Assembly;
         var thisAssembly = typeof(IFoo).Assembly;
         scriptOptions = scriptOptions.AddReferences(mscorlib, systemCore, thisAssembly);

         var result = CSharpScript.EvaluateAsync<Expression<Func<TInput, int>>>("it => it.Bar()", options: scriptOptions).Result;
         var compiledFunction = result.Compile();
         return compiledFunction;
      }
   }
}

How to use CSharpScript:
https://joshvarty.com/2015/10/15/learn-roslyn-now-part-14-intro-to-the-scripting-api/
https://www.strathweb.com/2018/01/easy-way-to-create-a-c-lambda-expression-from-a-string-with-roslyn/

Thursday, October 20, 2022
 
bgm
 
bgm
4

Given that there is no typo or some information that I am not aware of, I think the reason is that because you are trying to delete your class instance later rather than the QTimer instance allocated on the heap in the aforementioned method.

If you take a look at the the non-lambda version, that calls the deleteLater on the QTimer instance as that is the receiver in the connect call.

connect(timer, &QTimer::timeout, timer, &QTimer::deleteLater);

However, in the lambda variant, the timer instance is not captured and naturally, there would be no access to it in its current version, respectively. To make the two alternatives equivalent, this modification needs to be done to the code:

QObject::connect(timer, &QTimer::timeout, [this, timer](){
//                                               ^^^^^
    emit Log("Time out...");
    TestFunc(serverAddress, requestsFolderPath);
    timer->deleteLater();
//  ^^^^^^^
});
Saturday, October 22, 2022
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :