検索
ホームページバックエンド開発Python チュートリアルPython仮想マシンにおける辞書の実装原理は何ですか

辞書データ構造分析

/* The ma_values pointer is NULL for a combined table
 * or points to an array of PyObject* for a split table
 */
typedef struct {
    PyObject_HEAD
    Py_ssize_t ma_used;
    PyDictKeysObject *ma_keys;
    PyObject **ma_values;
} PyDictObject;
 
struct _dictkeysobject {
    Py_ssize_t dk_refcnt;
    Py_ssize_t dk_size;
    dict_lookup_func dk_lookup;
    Py_ssize_t dk_usable;
    PyDictKeyEntry dk_entries[1];
};
 
typedef struct {
    /* Cached hash code of me_key. */
    Py_hash_t me_hash;
    PyObject *me_key;
    PyObject *me_value; /* This field is only meaningful for combined tables */
} PyDictKeyEntry;

Python仮想マシンにおける辞書の実装原理は何ですか

上記の各フィールドの意味は次のとおりです:

  • ob_refcnt、オブジェクトの参照カウント。

  • ob_type、オブジェクトのデータ型。

  • ma_used、現在のハッシュ テーブル内のデータの数。

  • ma_keys は、キーと値のペアを保持する配列を指します。

  • ma_values、これは値の配列を指しますが、_dictkeysobject の PyDictKeyEntry 配列内のオブジェクトも値を格納できるため、この値は必ずしも cpython の特定の実装で使用されるわけではありません。この値は、すべてのキーが文字列である場合にのみ使用できます。この記事では、PyDictKeyEntry の値は主に辞書の実装について説明するために使用されるため、この変数は無視してかまいません。

  • dk_refcnt, これは参照カウントを表すためにも使用されます。これは辞書ビューに関連しています。原理は参照カウントと似ているため、ここでは無視します。

  • dk_size、これはハッシュ テーブルのサイズを表し、2n である必要があります。この場合、モジュラー演算はビット単位の AND 演算に変えることができます。

  • dk_lookup、これはハッシュ テーブルのルックアップ関数を表し、関数ポインターです。

  • dk_usable は、現在の配列で使用できるキーと値のペアの数を示します。

  • dk_entries、ハッシュ テーブル。キーと値のペアが実際に保存されます。

  • #ハッシュ テーブル全体のレイアウトは次のようになります。

#新しい辞書オブジェクトの作成Python仮想マシンにおける辞書の実装原理は何ですか

This この関数はまだ比較的単純で、最初にメモリ空間を適用し、次にいくつかの初期化操作を実行し、キーと値のペアを保存するためのハッシュ テーブルを適用します。

static PyObject *
dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *self;
    PyDictObject *d;
 
    assert(type != NULL && type->tp_alloc != NULL);
    // 申请内存空间
    self = type->tp_alloc(type, 0);
    if (self == NULL)
        return NULL;
    d = (PyDictObject *)self;
 
    /* The object has been implicitly tracked by tp_alloc */
    if (type == &PyDict_Type)
        _PyObject_GC_UNTRACK(d);
    // 因为还没有增加数据 因此哈希表当中 ma_used = 0
    d->ma_used = 0;
    // 申请保存键值对的数组  PyDict_MINSIZE_COMBINED 是一个宏定义 值为 8 表示哈希表数组的最小长度
    d->ma_keys = new_keys_object(PyDict_MINSIZE_COMBINED);
    // 如果申请失败返回 NULL
    if (d->ma_keys == NULL) {
        Py_DECREF(self);
        return NULL;
    }
    return self;
}
 
// new_keys_object 函数如下所示
static PyDictKeysObject *new_keys_object(Py_ssize_t size)
{
    PyDictKeysObject *dk;
    Py_ssize_t i;
    PyDictKeyEntry *ep0;
 
    assert(size >= PyDict_MINSIZE_SPLIT);
    assert(IS_POWER_OF_2(size));
    // 这里是申请内存的位置真正申请内存空间的大小为 PyDictKeysObject 的大小加上 size-1 个PyDictKeyEntry的大小
    // 这里你可能会有一位为啥不是 size 个 PyDictKeyEntry 的大小 因为在结构体 PyDictKeysObject 当中已经申请了一个 PyDictKeyEntry 对象了
    dk = PyMem_MALLOC(sizeof(PyDictKeysObject) +
                      sizeof(PyDictKeyEntry) * (size-1));
    if (dk == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    // 下面主要是一些初始化的操作 dk_refcnt 设置成 1 因为目前只有一个字典对象使用 这个 PyDictKeysObject 对象
    DK_DEBUG_INCREF dk->dk_refcnt = 1;
    dk->dk_size = size; // 哈希表的大小
    // 下面这行代码主要是表示哈希表当中目前还能存储多少个键值对 在 cpython 的实现当中允许有 2/3 的数组空间去存储数据 超过这个数则需要进行扩容
    dk->dk_usable = USABLE_FRACTION(size); // #define USABLE_FRACTION(n) ((((n) << 1)+1)/3)
    ep0 = &dk->dk_entries[0];
    /* Hash value of slot 0 is used by popitem, so it must be initialized */
    ep0->me_hash = 0;
    // 将所有的键值对初始化成 NULL
    for (i = 0; i < size; i++) {
        ep0[i].me_key = NULL;
        ep0[i].me_value = NULL;
    }
    dk->dk_lookup = lookdict_unicode_nodummy;
    return dk;
}

ハッシュ テーブルの拡張メカニズム

まず、ディクショナリ実装におけるハッシュ テーブルの拡張メカニズムを見てみましょう。ディクショナリに新しいデータを追加し続けると、ディクショナリは間もなく、その中のデータは配列の長さの 23 に達します。この時点で、それを拡張する必要があります。拡張後の配列のサイズは次のように計算されます:

#define GROWTH_RATE(d) (((d)->ma_used*2)+((d)->ma_keys->dk_size>>1))

新しい配列のサイズは次のようになります。元のキーと値のペアの数に 2 を乗算し、元の配列の長さの半分を加えた値。

一般に、拡張には 3 つの主な手順があります。

新しい配列のサイズを計算します。
  • 新しい配列を作成します。
  • 元のハッシュ テーブルのデータを新しい配列に追加します (つまり、再ハッシュ プロセス)。
  • 具体的な実装コードは次のとおりです。
  • static int
    insertion_resize(PyDictObject *mp)
    {
        return dictresize(mp, GROWTH_RATE(mp));
    }
     
    static int
    dictresize(PyDictObject *mp, Py_ssize_t minused)
    {
        Py_ssize_t newsize;
        PyDictKeysObject *oldkeys;
        PyObject **oldvalues;
        Py_ssize_t i, oldsize;
        // 下面的代码的主要作用就是计算得到能够大于等于 minused 最小的 2 的整数次幂
    /* Find the smallest table size > minused. */
        for (newsize = PyDict_MINSIZE_COMBINED;
             newsize <= minused && newsize > 0;
             newsize <<= 1)
            ;
        if (newsize <= 0) {
            PyErr_NoMemory();
            return -1;
        }
        oldkeys = mp->ma_keys;
        oldvalues = mp->ma_values;
        /* Allocate a new table. */
       // 创建新的数组
        mp->ma_keys = new_keys_object(newsize);
        if (mp->ma_keys == NULL) {
            mp->ma_keys = oldkeys;
            return -1;
        }
        if (oldkeys->dk_lookup == lookdict)
            mp->ma_keys->dk_lookup = lookdict;
        oldsize = DK_SIZE(oldkeys);
        mp->ma_values = NULL;
        /* If empty then nothing to copy so just return */
        if (oldsize == 1) {
            assert(oldkeys == Py_EMPTY_KEYS);
            DK_DECREF(oldkeys);
            return 0;
        }
        /* Main loop below assumes we can transfer refcount to new keys
         * and that value is stored in me_value.
         * Increment ref-counts and copy values here to compensate
         * This (resizing a split table) should be relatively rare */
        if (oldvalues != NULL) {
            for (i = 0; i < oldsize; i++) {
                if (oldvalues[i] != NULL) {
                    Py_INCREF(oldkeys->dk_entries[i].me_key);
                    oldkeys->dk_entries[i].me_value = oldvalues[i];
                }
            }
        }
        /* Main loop */
        // 将原来数组当中的元素加入到新的数组当中
        for (i = 0; i < oldsize; i++) {
            PyDictKeyEntry *ep = &oldkeys->dk_entries[i];
            if (ep->me_value != NULL) {
                assert(ep->me_key != dummy);
                insertdict_clean(mp, ep->me_key, ep->me_hash, ep->me_value);
            }
        }
        // 更新一下当前哈希表当中能够插入多少数据
        mp->ma_keys->dk_usable -= mp->ma_used;
        if (oldvalues != NULL) {
            /* NULL out me_value slot in oldkeys, in case it was shared */
            for (i = 0; i < oldsize; i++)
                oldkeys->dk_entries[i].me_value = NULL;
            assert(oldvalues != empty_values);
            free_values(oldvalues);
            DK_DECREF(oldkeys);
        }
        else {
            assert(oldkeys->dk_lookup != lookdict_split);
            if (oldkeys->dk_lookup != lookdict_unicode_nodummy) {
                PyDictKeyEntry *ep0 = &oldkeys->dk_entries[0];
                for (i = 0; i < oldsize; i++) {
                    if (ep0[i].me_key == dummy)
                        Py_DECREF(dummy);
                }
            }
            assert(oldkeys->dk_refcnt == 1);
            DK_DEBUG_DECREF PyMem_FREE(oldkeys);
        }
        return 0;
    }
ディクショナリへのデータの挿入

ディクショナリへのデータの挿入を続けると、おそらく、ハッシュの競合が発生した場合、ハッシュの競合を処理するディクショナリのメソッドは、ハッシュの競合を処理するためにコレクションで使用されるメソッドと基本的に同じです。どちらも開発アドレスのメソッドを使用します。ただし、このオープン アドレスの実装は、この方法は比較的複雑ですが、具体的な手順は以下の通りです。

以上がPython仮想マシンにおける辞書の実装原理は何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事は亿速云で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。
リストと配列の選択は、大規模なデータセットを扱うPythonアプリケーションの全体的なパフォーマンスにどのように影響しますか?リストと配列の選択は、大規模なデータセットを扱うPythonアプリケーションの全体的なパフォーマンスにどのように影響しますか?May 03, 2025 am 12:11 AM

forhandlinglaredataSetsinpython、usenumpyArrays forbetterperformance.1)numpyarraysarememory-effictientandfasterfornumericaloperations.2)nusinnnnedarytypeconversions.3)レバレッジベクトル化は、測定済みのマネージメーシェイメージーウェイズデイタイです

Pythonのリストと配列にメモリがどのように割り当てられるかを説明します。Pythonのリストと配列にメモリがどのように割り当てられるかを説明します。May 03, 2025 am 12:10 AM

inpython、listsusedynamicmemoryallocation with allocation、whilenumpyArraysalocatefixedmemory.1)listsallocatemorememorythanneededededinitivative.2)numpyArrayasallocateexactmemoryforements、rededicablebutlessflexibilityを提供します。

Pythonアレイ内の要素のデータ型をどのように指定しますか?Pythonアレイ内の要素のデータ型をどのように指定しますか?May 03, 2025 am 12:06 AM

inpython、youcanspecthedatatypeyfelemeremodelernspant.1)usenpynernrump.1)usenpynerp.dloatp.ploatm64、フォーマーpreciscontrolatatypes。

Numpyとは何ですか、そしてなぜPythonの数値コンピューティングにとって重要なのですか?Numpyとは何ですか、そしてなぜPythonの数値コンピューティングにとって重要なのですか?May 03, 2025 am 12:03 AM

numpyisessentialfornumericalcomputinginpythonduetoitsspeed、memory efficiency、andcomprehensivematicalfunctions.1)それは、performsoperations.2)numpyArraysaremoremory-efficientthanpythonlists.3)Itofderangeofmathematicaloperty

「隣接するメモリ割り当て」の概念と、配列にとってその重要性について説明します。「隣接するメモリ割り当て」の概念と、配列にとってその重要性について説明します。May 03, 2025 am 12:01 AM

contiguousMemoryAllocationisucial forArraysは、ForeffienceAndfastelementAccess.1)iteenablesConstantTimeAccess、O(1)、DuetodirectAddresscalculation.2)itemprovesefficiencyByAllowingMultiblementFechesperCacheLine.3)itimplifieMememm

Pythonリストをどのようにスライスしますか?Pythonリストをどのようにスライスしますか?May 02, 2025 am 12:14 AM

slicingapythonlistisdoneusingtheyntaxlist [start:stop:step] .hore'showitworks:1)startisthe indexofthefirstelementtoinclude.2)spotisthe indexofthefirmenttoeexclude.3)staptistheincrementbetbetinelements

Numpyアレイで実行できる一般的な操作は何ですか?Numpyアレイで実行できる一般的な操作は何ですか?May 02, 2025 am 12:09 AM

numpyallows forvariousoperationsonarrays:1)basicarithmeticlikeaddition、減算、乗算、および分割; 2)AdvancedperationssuchasmatrixMultiplication;

Pythonを使用したデータ分析では、配列はどのように使用されていますか?Pythonを使用したデータ分析では、配列はどのように使用されていますか?May 02, 2025 am 12:09 AM

Arraysinpython、特にnumpyandpandas、aresentialfordataanalysis、offeringspeedandeficiency.1)numpyarraysenable numpyarraysenable handling forlaredatasents andcomplexoperationslikemoverages.2)Pandasextendsnumpy'scapabivitieswithdataframesfortruc

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

MantisBT

MantisBT

Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

このプロジェクトは osdn.net/projects/mingw に移行中です。引き続きそこでフォローしていただけます。 MinGW: GNU Compiler Collection (GCC) のネイティブ Windows ポートであり、ネイティブ Windows アプリケーションを構築するための自由に配布可能なインポート ライブラリとヘッダー ファイルであり、C99 機能をサポートする MSVC ランタイムの拡張機能が含まれています。すべての MinGW ソフトウェアは 64 ビット Windows プラットフォームで実行できます。

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター