Use MurmurHash3_x64_128 (MMH3_HASH) for polygon checksum.

Improves performance when moving footprints.

See https://gitlab.com/kicad/code/kicad/-/issues/18148
This commit is contained in:
dsa-t 2024-06-24 21:19:43 +00:00
parent 8f02944437
commit cb41b53ab7
10 changed files with 499 additions and 31 deletions

View File

@ -47,7 +47,7 @@
#include <geometry/shape_line_chain.h>
#include <math/box2.h> // for BOX2I
#include <math/vector2d.h> // for VECTOR2I
#include <md5_hash.h>
#include <hash_128.h>
/**
@ -552,7 +552,7 @@ public:
}
bool IsTriangulationUpToDate() const;
MD5_HASH GetHash() const;
HASH_128 GetHash() const;
virtual bool HasIndexableSubshapes() const override;
@ -1568,7 +1568,7 @@ private:
/// Return true if the polygon set has any holes that touch share a vertex.
bool hasTouchingHoles( const POLYGON& aPoly ) const;
MD5_HASH checksum() const;
HASH_128 checksum() const;
protected:
std::vector<POLYGON> m_polys;
@ -1578,7 +1578,8 @@ protected:
std::mutex m_triangulationMutex;
private:
MD5_HASH m_hash;
HASH_128 m_hash;
bool m_hashValid = false;
};
#endif // __SHAPE_POLY_SET_H

View File

@ -0,0 +1,54 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 Alex Shvartzkop <dudesuchamazing@gmail.com>
* Copyright (C) 2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef HASH_128_H_
#define HASH_128_H_
#include <cstdint>
/**
* A storage class for 128-bit hash value
*/
struct HASH_128
{
void Clear()
{
memset( Value64, 0, sizeof( Value64 ) ); //
}
bool operator==( const HASH_128& aOther ) const
{
return memcmp( Value64, aOther.Value64, sizeof( Value64 ) ) == 0;
}
public:
union
{
uint8_t Value8[16]{}; // Zero-initialized
uint32_t Value32[4];
uint64_t Value64[2];
};
};
#endif // HASH_128_H_

View File

@ -0,0 +1,196 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 Alex Shvartzkop <dudesuchamazing@gmail.com>
* Copyright (C) 2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
// Based on https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
#ifndef MMH3_HASH_H_
#define MMH3_HASH_H_
#include <hash_128.h>
//-----------------------------------------------------------------------------
// Platform-specific functions and macros
// Microsoft Visual Studio
#if defined( _MSC_VER )
#define FORCE_INLINE __forceinline
#include <stdlib.h>
#define ROTL64( x, y ) _rotl64( x, y )
#define BIG_CONSTANT( x ) ( x )
// Other compilers
#else // defined(_MSC_VER)
#define FORCE_INLINE inline __attribute__( ( always_inline ) )
inline uint64_t rotl64( uint64_t x, int8_t r )
{
return ( x << r ) | ( x >> ( 64 - r ) );
}
#define ROTL64( x, y ) rotl64( x, y )
#define BIG_CONSTANT( x ) ( x##LLU )
#endif // !defined(_MSC_VER)
/**
* A streaming C++ equivalent for MurmurHash3_x64_128.
*/
class MMH3_HASH
{
public:
MMH3_HASH( uint32_t aSeed = 0 ) { reset( aSeed ); }
FORCE_INLINE void reset( uint32_t aSeed = 0 )
{
h1 = aSeed;
h2 = aSeed;
len = 0;
}
FORCE_INLINE void add( int32_t input )
{
blocks[( len % 16 ) / 4] = input;
len += 4;
if( len % 16 == 0 )
hashBlock();
}
FORCE_INLINE HASH_128 digest()
{
hashTail();
HASH_128 h128;
hashFinal( h128 );
return h128;
}
private:
// Block read - if your platform needs to do endian-swapping or can only
// handle aligned reads, do the conversion here
FORCE_INLINE uint64_t getblock64( int i )
{
return blocks64[i]; //
}
FORCE_INLINE void hashBlock()
{
static const uint64_t c1 = BIG_CONSTANT( 0x87c37b91114253d5 );
static const uint64_t c2 = BIG_CONSTANT( 0x4cf5ad432745937f );
uint64_t k1 = getblock64( 0 );
uint64_t k2 = getblock64( 1 );
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
}
FORCE_INLINE void hashTail()
{
const uint8_t * tail = (const uint8_t*)(blocks);
static const uint64_t c1 = BIG_CONSTANT( 0x87c37b91114253d5 );
static const uint64_t c2 = BIG_CONSTANT( 0x4cf5ad432745937f );
uint64_t k1 = 0;
uint64_t k2 = 0;
switch( len & 15)
{
case 15: k2 ^= ((uint64_t)tail[14]) << 48;
case 14: k2 ^= ((uint64_t)tail[13]) << 40;
case 13: k2 ^= ((uint64_t)tail[12]) << 32;
case 12: k2 ^= ((uint64_t)tail[11]) << 24;
case 11: k2 ^= ((uint64_t)tail[10]) << 16;
case 10: k2 ^= ((uint64_t)tail[ 9]) << 8;
case 9: k2 ^= ((uint64_t)tail[ 8]) << 0;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
case 8: k1 ^= ((uint64_t)tail[ 7]) << 56;
case 7: k1 ^= ((uint64_t)tail[ 6]) << 48;
case 6: k1 ^= ((uint64_t)tail[ 5]) << 40;
case 5: k1 ^= ((uint64_t)tail[ 4]) << 32;
case 4: k1 ^= ((uint64_t)tail[ 3]) << 24;
case 3: k1 ^= ((uint64_t)tail[ 2]) << 16;
case 2: k1 ^= ((uint64_t)tail[ 1]) << 8;
case 1: k1 ^= ((uint64_t)tail[ 0]) << 0;
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
};
}
// Finalization mix - force all bits of a hash block to avalanche
static FORCE_INLINE uint64_t fmix64( uint64_t k )
{
k ^= k >> 33;
k *= BIG_CONSTANT( 0xff51afd7ed558ccd );
k ^= k >> 33;
k *= BIG_CONSTANT( 0xc4ceb9fe1a85ec53 );
k ^= k >> 33;
return k;
}
FORCE_INLINE void hashFinal( HASH_128& out )
{
h1 ^= len;
h2 ^= len;
h1 += h2;
h2 += h1;
h1 = fmix64( h1 );
h2 = fmix64( h2 );
h1 += h2;
h2 += h1;
out.Value64[0] = h1;
out.Value64[1] = h2;
}
private:
uint64_t h1;
uint64_t h2;
union
{
uint32_t blocks[4];
uint64_t blocks64[2];
};
uint32_t len;
};
#undef FORCE_INLINE
#undef ROTL64
#undef BIG_CONSTANT
#endif // MMH3_HASH_H_

View File

@ -53,8 +53,8 @@
#include <math/box2.h> // for BOX2I
#include <math/util.h> // for KiROUND, rescale
#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
#include <md5_hash.h>
#include <hash.h>
#include <mmh3_hash.h>
#include <geometry/shape_segment.h>
#include <geometry/shape_circle.h>
@ -111,12 +111,14 @@ SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_POLY_SET& aOther ) :
}
m_hash = aOther.GetHash();
m_hashValid = true;
m_triangulationValid = true;
}
else
{
m_triangulationValid = false;
m_hash = MD5_HASH();
m_hash.Clear();
m_hashValid = false;
m_triangulatedPolys.clear();
}
}
@ -127,7 +129,8 @@ SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_POLY_SET& aOther, DROP_TRIANGULATION
m_polys( aOther.m_polys )
{
m_triangulationValid = false;
m_hash = MD5_HASH();
m_hash.Clear();
m_hashValid = false;
m_triangulatedPolys.clear();
}
@ -2523,7 +2526,10 @@ void SHAPE_POLY_SET::DeletePolygonAndTriangulationData( int aIdx, bool aUpdateHa
}
if( aUpdateHash )
{
m_hash = checksum();
m_hashValid = true;
}
}
}
@ -2531,6 +2537,7 @@ void SHAPE_POLY_SET::DeletePolygonAndTriangulationData( int aIdx, bool aUpdateHa
void SHAPE_POLY_SET::UpdateTriangulationDataHash()
{
m_hash = checksum();
m_hashValid = true;
}
@ -2723,6 +2730,7 @@ void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
tri->Move( aVector );
m_hash = checksum();
m_hashValid = true;
}
@ -3131,15 +3139,16 @@ SHAPE_POLY_SET &SHAPE_POLY_SET::operator=( const SHAPE_POLY_SET& aOther )
}
m_hash = aOther.m_hash;
m_hashValid = aOther.m_hashValid;
m_triangulationValid = aOther.m_triangulationValid.load();
return *this;
}
MD5_HASH SHAPE_POLY_SET::GetHash() const
HASH_128 SHAPE_POLY_SET::GetHash() const
{
if( !m_hash.IsValid() )
if( !m_hashValid )
return checksum();
return m_hash;
@ -3151,10 +3160,10 @@ bool SHAPE_POLY_SET::IsTriangulationUpToDate() const
if( !m_triangulationValid )
return false;
if( !m_hash.IsValid() )
if( !m_hashValid )
return false;
MD5_HASH hash = checksum();
HASH_128 hash = checksum();
return hash == m_hash;
}
@ -3234,7 +3243,7 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
{
std::unique_lock<std::mutex> lock( m_triangulationMutex );
if( m_triangulationValid && m_hash.IsValid() )
if( m_triangulationValid && m_hashValid )
{
if( m_hash == checksum() )
return;
@ -3242,7 +3251,7 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
// Invalidate, in case anything goes wrong below
m_triangulationValid = false;
m_hash.SetValid( false );
m_hashValid = false;
auto triangulate =
[]( SHAPE_POLY_SET& polySet, int forOutline,
@ -3334,6 +3343,7 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
else
{
m_hash = checksum();
m_hashValid = true;
// Set valid flag only after everything has been updated
m_triangulationValid = true;
}
@ -3354,6 +3364,7 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
else
{
m_hash = checksum();
m_hashValid = true;
// Set valid flag only after everything has been updated
m_triangulationValid = true;
}
@ -3361,31 +3372,31 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
}
MD5_HASH SHAPE_POLY_SET::checksum() const
HASH_128 SHAPE_POLY_SET::checksum() const
{
MD5_HASH hash;
MMH3_HASH hash( 0x68AF835D ); // Arbitrary seed
hash.Hash( m_polys.size() );
hash.add( m_polys.size() );
for( const POLYGON& outline : m_polys )
{
hash.Hash( outline.size() );
hash.add( outline.size() );
for( const SHAPE_LINE_CHAIN& lc : outline )
{
hash.Hash( lc.PointCount() );
hash.add( lc.PointCount() );
for( int i = 0; i < lc.PointCount(); i++ )
{
hash.Hash( lc.CPoint( i ).x );
hash.Hash( lc.CPoint( i ).y );
VECTOR2I pt = lc.CPoint( i );
hash.add( pt.x );
hash.add( pt.y );
}
}
}
hash.Finalize();
return hash;
return hash.digest();
}

View File

@ -1061,8 +1061,8 @@ private:
SHAPE_POLY_SET m_courtyard_cache_front; // Note that a footprint can have both front and back
SHAPE_POLY_SET m_courtyard_cache_back; // courtyards populated.
mutable MD5_HASH m_courtyard_cache_front_hash;
mutable MD5_HASH m_courtyard_cache_back_hash;
mutable HASH_128 m_courtyard_cache_front_hash;
mutable HASH_128 m_courtyard_cache_back_hash;
mutable std::mutex m_courtyard_cache_mutex;
};

View File

@ -576,12 +576,16 @@ PADSTACK* SPECCTRA_DB::makePADSTACK( BOARD* aBoard, PAD* aPad )
}
// this string _must_ be unique for a given physical shape, so try to make it unique
MD5_HASH hash = pad_shape.GetHash();
const HASH_128 hash = pad_shape.GetHash();
char hashStr[33] = {};
snprintf( hashStr, sizeof( hashStr ), "%016llX%016llX", hash.Value64[0], hash.Value64[1] );
BOX2I rect = aPad->GetBoundingBox();
snprintf( name, sizeof( name ), "Cust%sPad_%.6gx%.6g_%.6gx_%.6g_%d_um_%s",
uniqifier.c_str(), IU2um( aPad->GetSize().x ), IU2um( aPad->GetSize().y ),
IU2um( rect.GetWidth() ), IU2um( rect.GetHeight() ), (int) polygonal_shape.size(),
hash.Format( true ).c_str() );
hashStr );
name[sizeof( name ) - 1] = 0;
padstack->SetPadstackId( name );

View File

@ -406,7 +406,7 @@ void ZONE::SetCornerRadius( unsigned int aRadius )
static SHAPE_POLY_SET g_nullPoly;
MD5_HASH ZONE::GetHashValue( PCB_LAYER_ID aLayer )
HASH_128 ZONE::GetHashValue( PCB_LAYER_ID aLayer )
{
if( !m_filledPolysHash.count( aLayer ) )
return g_nullPoly.GetHash();

View File

@ -782,7 +782,7 @@ public:
/**
* @return the hash value previously calculated by BuildHashValue().
*/
MD5_HASH GetHashValue( PCB_LAYER_ID aLayer );
HASH_128 GetHashValue( PCB_LAYER_ID aLayer );
double Similarity( const BOARD_ITEM& aOther ) const override;
@ -904,7 +904,7 @@ protected:
LSET m_fillFlags;
/// A hash value used in zone filling calculations to see if the filled areas are up to date
std::map<PCB_LAYER_ID, MD5_HASH> m_filledPolysHash;
std::map<PCB_LAYER_ID, HASH_128> m_filledPolysHash;
ZONE_BORDER_DISPLAY_STYLE m_borderStyle; // border display style, see enum above
int m_borderHatchPitch; // for DIAGONAL_EDGE, distance between 2 lines

View File

@ -92,7 +92,7 @@ bool ZONE_FILLER::Fill( const std::vector<ZONE*>& aZones, bool aCheck, wxWindow*
std::lock_guard<KISPINLOCK> lock( m_board->GetConnectivity()->GetLock() );
std::vector<std::pair<ZONE*, PCB_LAYER_ID>> toFill;
std::map<std::pair<ZONE*, PCB_LAYER_ID>, MD5_HASH> oldFillHashes;
std::map<std::pair<ZONE*, PCB_LAYER_ID>, HASH_128> oldFillHashes;
std::map<ZONE*, std::map<PCB_LAYER_ID, ISOLATED_ISLANDS>> isolatedIslandsMap;
std::shared_ptr<CONNECTIVITY_DATA> connectivity = m_board->GetConnectivity();

View File

@ -29,6 +29,7 @@
// Code under test
#include <trigo.h>
#include <hash_128.h>
#include <geometry/shape_arc.h>
@ -317,4 +318,205 @@ BOOST_AUTO_TEST_CASE( TestInterceptsNegativeX )
}
BOOST_AUTO_TEST_CASE( TestHash128 )
{
const HASH_128 zero;
for( size_t i = 0; i < 16; i++ )
BOOST_CHECK( zero.Value8[i] == 0 );
HASH_128* zero_h = new HASH_128;
for( size_t i = 0; i < 16; i++ )
BOOST_CHECK( zero_h->Value8[i] == 0 );
delete zero_h;
HASH_128 h;
h.Value64[0] = 0x00CDEF0123456789ULL;
h.Value64[1] = 0x56789ABCDEF01234ULL;
BOOST_CHECK( h != zero );
HASH_128 b = h;
BOOST_CHECK( b != zero );
BOOST_CHECK( b == h );
BOOST_CHECK( h == b );
char hashStr[33] = {};
snprintf( hashStr, sizeof( hashStr ), "%016llX%016llX", h.Value64[0], h.Value64[1] );
BOOST_CHECK( std::string( hashStr ) == std::string( "00CDEF012345678956789ABCDEF01234" ) );
}
//-----------------------------------------------------------------------------
// Test MMH3_HASH against the original MurmurHash3_x64_128 implementation
#include <mmh3_hash.h>
void MurmurHash3_x64_128( const void* key, const int len, const uint32_t seed, void* out );
BOOST_AUTO_TEST_CASE( TestMMH3Hash )
{
std::vector<std::vector<int32_t>> data;
for( size_t i = 0; i < 10; i++ )
{
std::vector<int32_t>& vec = data.emplace_back();
size_t vecSize = rand() % 128;
for( size_t j = 0; j < vecSize; j++ )
vec.emplace_back( rand() );
}
int64_t seed = 0; // Test 0 seed first
for( const std::vector<int32_t>& vec : data )
{
MMH3_HASH mmh3( seed );
for( const int32_t val : vec )
mmh3.add( val );
HASH_128 h128 = mmh3.digest();
HASH_128 orig128;
MurmurHash3_x64_128( (void*) vec.data(), vec.size() * sizeof( vec[0] ), seed,
(void*) orig128.Value64 );
BOOST_CHECK( h128 == orig128 );
// Generate new random seed
seed = rand();
}
}
//-----------------------------------------------------------------------------
// The original MurmurHash3_x64_128 implementation
// Microsoft Visual Studio
#if defined( _MSC_VER )
#define FORCE_INLINE __forceinline
#include <stdlib.h>
#define ROTL64( x, y ) _rotl64( x, y )
#define BIG_CONSTANT( x ) ( x )
// Other compilers
#else // defined(_MSC_VER)
#define FORCE_INLINE inline __attribute__( ( always_inline ) )
inline uint64_t rotl64( uint64_t x, int8_t r )
{
return ( x << r ) | ( x >> ( 64 - r ) );
}
#define ROTL64( x, y ) rotl64( x, y )
#define BIG_CONSTANT( x ) ( x##LLU )
#endif // !defined(_MSC_VER)
FORCE_INLINE uint64_t getblock64 ( const uint64_t * p, int i )
{
return p[i];
}
FORCE_INLINE uint64_t fmix64 ( uint64_t k )
{
k ^= k >> 33;
k *= BIG_CONSTANT(0xff51afd7ed558ccd);
k ^= k >> 33;
k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
k ^= k >> 33;
return k;
}
void MurmurHash3_x64_128 ( const void * key, const int len,
const uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 16;
uint64_t h1 = seed;
uint64_t h2 = seed;
const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
//----------
// body
const uint64_t * blocks = (const uint64_t *)(data);
for(int i = 0; i < nblocks; i++)
{
uint64_t k1 = getblock64(blocks,i*2+0);
uint64_t k2 = getblock64(blocks,i*2+1);
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
uint64_t k1 = 0;
uint64_t k2 = 0;
switch(len & 15)
{
case 15: k2 ^= ((uint64_t)tail[14]) << 48;
case 14: k2 ^= ((uint64_t)tail[13]) << 40;
case 13: k2 ^= ((uint64_t)tail[12]) << 32;
case 12: k2 ^= ((uint64_t)tail[11]) << 24;
case 11: k2 ^= ((uint64_t)tail[10]) << 16;
case 10: k2 ^= ((uint64_t)tail[ 9]) << 8;
case 9: k2 ^= ((uint64_t)tail[ 8]) << 0;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
case 8: k1 ^= ((uint64_t)tail[ 7]) << 56;
case 7: k1 ^= ((uint64_t)tail[ 6]) << 48;
case 6: k1 ^= ((uint64_t)tail[ 5]) << 40;
case 5: k1 ^= ((uint64_t)tail[ 4]) << 32;
case 4: k1 ^= ((uint64_t)tail[ 3]) << 24;
case 3: k1 ^= ((uint64_t)tail[ 2]) << 16;
case 2: k1 ^= ((uint64_t)tail[ 1]) << 8;
case 1: k1 ^= ((uint64_t)tail[ 0]) << 0;
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
};
//----------
// finalization
h1 ^= len; h2 ^= len;
h1 += h2;
h2 += h1;
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 += h2;
h2 += h1;
((uint64_t*)out)[0] = h1;
((uint64_t*)out)[1] = h2;
}
//-----------------------------------------------------------------------------
#undef FORCE_INLINE
#undef ROTL64
#undef BIG_CONSTANT
BOOST_AUTO_TEST_SUITE_END()