HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Improving performance of A* search in PHP

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
phpperformanceimprovingsearch

Problem

I need to figure out what can be done to improve the performance of a A* search algorithm in PHP. The idea is to look for the shortest path, in number of hops, between two points in a "map". Actual distance is irrelevant.

This is a minimalistic functional version of what I'm doing:

 [2, 4, 6],
        2 => [1, 3],
        3 => [2, 5],
        4 => [1, 5, 6],
        5 => [3, 4, 7],
        6 => [1, 4],
        7 => [5]
    ];

    function points_pathfind($from, $to)
    {
        global $points;

        if (!isset($points[$from]) || !isset($points[$to]))
            return null;

        if ($from == $to)
            return [$from];

        $seen = [$from];  // improves peformance
        $queue = [];
        $step = [$from, null];
        for (;;)
        {
            if ($queue)
            {
                $step = array_shift($queue);
                if ($step[0] == $to)
                {
                    $result = [];
                    while ($step !== null)
                    {
                        $result[] = $step[0];
                        $step = $step[1];
                    }
                    return array_reverse($result);
                }
            }
            foreach ($points[$step[0]] as $point)
            {
                if (!in_array($point, $seen))
                {
                    $seen[]  = $point;
                    $queue[] = [$point, $step];
                }
            }

            if (!$queue)
                return null;
        }
    }

    echo implode(' -=> ', points_pathfind(1, 7));


Output:


1 -=> 4 -=> 5 -=> 7

The actual code howover will deal with almost 8000 points with a total of over 15000 connections, in which it all become extremely slow to the point that mapping some paths can take a couple seconds. This is unacceptable to my application. I even considered caching all possibilities to a database (which I will have to do anyway) but it is so slow it would take days only to generate

Solution

I managed to greatly increase performance by avoiding array resizing and memory reallocation. By allocating all array space I need by start and reusing those arrays instead of recreating and resizing them, the performance of the code has dramatically changed from one search every couple seconds to almost a hundred searches per second.

 [2, 4, 6],
        2 => [1, 3],
        3 => [2, 5],
        4 => [1, 5, 6],
        5 => [3, 4, 7],
        6 => [1, 4],
        7 => [5]
    ];

    $seen = $queue = [];
    foreach ($points as $key => $val)
    {
        $seen[$key] = 0;
        $queue[] = [0, -1];
    }

    function points_pathfind($from, $to)
    {
        global $points, $seen, $queue;

        if (!isset($points[$from]) || !isset($points[$to]))
            return null;

        if ($from == $to)
            return [$from];

        $queue[0][0] = $from;

        array_walk($seen, function (&$a) { $a = 0; });
        $seen[$from] = 1;

        for ($cur = $end = 0; $cur  ', points_pathfind(1, 7));


The general approach of the algorithm howover is still the same. I wonder if there is a more intelligent way.

Code Snippets

<?php

    /*

        1 ____ 2 ____ 3 ___ 5
        | \            ____/|
        |  \    ______/     |
        |   4__/            |
        |  /                7
        | /                 
        6                   

    */

    $points = [
        1 => [2, 4, 6],
        2 => [1, 3],
        3 => [2, 5],
        4 => [1, 5, 6],
        5 => [3, 4, 7],
        6 => [1, 4],
        7 => [5]
    ];

    $seen = $queue = [];
    foreach ($points as $key => $val)
    {
        $seen[$key] = 0;
        $queue[] = [0, -1];
    }

    function points_pathfind($from, $to)
    {
        global $points, $seen, $queue;

        if (!isset($points[$from]) || !isset($points[$to]))
            return null;

        if ($from == $to)
            return [$from];

        $queue[0][0] = $from;

        array_walk($seen, function (&$a) { $a = 0; });
        $seen[$from] = 1;

        for ($cur = $end = 0; $cur <= $end; ++$cur)    
        {
            foreach ($points[$queue[$cur][0]] as $next)
            {
                if (!$seen[$next])
                {
                    if ($next == $to)
                    {
                        $result = [$to];
                        for ($jmp = $cur; $jmp != -1; $jmp = $queue[$jmp][1])
                            $result[] = $queue[$jmp][0];
                        return array_reverse($result);
                    }

                    ++$end;
                    $seen[$next]    = 1;
                    $queue[$end][0] = $next;
                    $queue[$end][1] = $cur;
                }
            }
        }
    }

    echo implode(' -=> ', points_pathfind(1, 7));

Context

StackExchange Code Review Q#62233, answer score: 5

Revisions (0)

No revisions yet.