Skip to content

Eloquent performance: improve caching in HasAttributes::getCastType() and HasAttributes::isEncryptedCastable() #57045

@thomas-0816

Description

@thomas-0816

Laravel Version

12.28

PHP Version

8.4.12

Database Driver & Version

mariadb 10.11.13

Description

I profiled a long running job with spx and got:

*** SPX Report ***

Global stats:

  Called functions    :   436.7M
  Distinct functions  :     3.1K

  Wall time           :   53.16s
  ZE memory usage     :  237.5MB

Flat profile:

 Wall time           | ZE memory usage     |
 Inc.     | *Exc.    | Inc.     | Exc.     | Called   | Function
----------+----------+----------+----------+----------+----------
   12.80s |    3.52s |     376B |  -42.8GB |    25.7M | Illuminate\Database\Eloquent\Model::getCastType
   15.63s |    1.85s |     376B |       0B |     8.3M | Illuminate\Database\Eloquent\Model::castAttribute
   12.21s |  925.6ms |       0B |       0B |     2.7M | Illuminate\Database\Eloquent\Model::originalIsEquivalent
   22.95s |  813.9ms |    9.6KB |       0B |     6.5M | Illuminate\Database\Eloquent\Model::getAttributeValue
    8.41s |  760.7ms |  203.5MB |  203.5MB |     1.1M | Illuminate\Database\Eloquent\Model::setAttribute
    3.98s |  441.6ms |      80B |       0B |   492.4K | Illuminate\Support\Collection::{closure:Illuminate\Support\Traits\EnumeratesValues::operatorForWhere():1101}
    3.46s |  340.8ms |       0B |       0B |     2.3M | Illuminate\Database\Eloquent\Model::isDateAttribute
    5.90s |  273.9ms |   43.5MB |   -1.0MB |    25.7K | Illuminate\Support\Collection::groupBy

then I changed the HasAttributes::getCastType() from:

    protected function getCastType($key)
    {
        $castType = $this->getCasts()[$key];

        if (isset(static::$castTypeCache[$castType])) {
            return static::$castTypeCache[$castType];
        }

        if ($this->isCustomDateTimeCast($castType)) {
            $convertedCastType = 'custom_datetime';
        } elseif ($this->isImmutableCustomDateTimeCast($castType)) {
            $convertedCastType = 'immutable_custom_datetime';
        } elseif ($this->isDecimalCast($castType)) {
            $convertedCastType = 'decimal';
        } elseif (class_exists($castType)) {
            $convertedCastType = $castType;
        } else {
            $convertedCastType = trim(strtolower($castType));
        }

        return static::$castTypeCache[$castType] = $convertedCastType;
    }

to:

    protected function getCastType($key)
    {
        if (isset(static::$castTypeCache[static::class][$key])) {
            return static::$castTypeCache[static::class][$key];
        }

        $castType = $this->getCasts()[$key];

        if ($this->isCustomDateTimeCast($castType)) {
            $convertedCastType = 'custom_datetime';
        } elseif ($this->isImmutableCustomDateTimeCast($castType)) {
            $convertedCastType = 'immutable_custom_datetime';
        } elseif ($this->isDecimalCast($castType)) {
            $convertedCastType = 'decimal';
        } elseif (class_exists($castType)) {
            $convertedCastType = $castType;
        } else {
            $convertedCastType = trim(strtolower($castType));
        }

        return static::$castTypeCache[static::class][$key] = $convertedCastType;
    }

The runtime of the job went from 21.7s to 17.7s (without spx).

Then I changed HasAttributes::isEncryptedCastable() from:

    protected function isEncryptedCastable($key)
    {
        return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
    }

to:

    protected static $encryptedCastableCache = [];

    protected function isEncryptedCastable($key)
    {
        if (isset(static::$encryptedCastableCache[static::class][$key])) {
            return static::$encryptedCastableCache[static::class][$key];
        }

        return static::$encryptedCastableCache[static::class][$key] = $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
    }

The runtime of the job went from 17.7s to 15.1s (without spx). So a total of 30% faster.

What do you think about these changes?

Note: I can't provide a PR for this, but you and the community are free to create a PR for this.

Steps To Reproduce

Run a script using Eloquent with many Model instances.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions