Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10769] ファイルの概要

このコミットは、Go言語のランタイムにおけるガベージコレクション(GC)のテスト gc_test.go の修正に関するものです。具体的には、TestGcSys 関数がメモリ使用量をテストする方法を変更し、総メモリ使用量ではなく、追加で割り当てられたメモリ空間をテストするように修正しています。

コミット

  • コミットハッシュ: 26239417bb0973c658a7d05e7c8b0b058562ccb8
  • 作者: Ian Lance Taylor iant@golang.org
  • コミット日時: 2011年12月13日 15:12:55 -0800

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/26239417bb0973c658a7d05e7c8b0b058562ccb8

元コミット内容

runtime: Make gc_test test extra allocated space, not total space.

Testing total space fails for gccgo when not using split
stacks, because then each goroutine has a large stack, and so
the total memory usage is large.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5487068

変更の背景

この変更の背景には、Go言語の異なるコンパイラ実装であるgccgoの特性が関係しています。Go言語には主に公式のgcコンパイラと、GCCフロントエンドとして実装されたgccgoコンパイラが存在します。

元のgc_test.goTestGcSysテストは、プログラム全体の総メモリ使用量(runtime.MemStats.Sys)をチェックしていました。しかし、gccgoコンパイラを使用し、かつ「スプリットスタック(Split Stacks)」が有効になっていない環境では、各ゴルーチン(goroutine)が初期段階で大きなスタック空間を割り当ててしまうという問題がありました。

スプリットスタックは、Goのランタイムがゴルーチンのスタックサイズを動的に拡張・縮小するメカニズムです。これにより、初期のスタック割り当てを小さく保ち、必要に応じて拡張することでメモリ効率を高めます。しかし、gccgoがスプリットスタックをサポートしていない、または無効になっている場合、ゴルーチンごとに固定の大きなスタックが割り当てられるため、たとえGCが適切に動作していても、総メモリ使用量が非常に大きくなってしまい、テストが不必要に失敗する原因となっていました。

このコミットは、このようなgccgo環境でのテストの誤検出を避けるため、テストの焦点を「総メモリ使用量」から「GCによって解放されずに残った追加の割り当てメモリ量」へと変更することを目的としています。これにより、コンパイラの実装やスタック管理の方式に依存せず、GCの効率性をより正確に評価できるようになります。

前提知識の解説

Go言語のガベージコレクション (GC)

Go言語は自動メモリ管理(ガベージコレクション)を採用しています。開発者は手動でメモリを解放する必要がありません。GoのGCは並行・世代別・マーク&スイープ方式をベースとしており、プログラムの実行と並行して動作し、アプリケーションの停止時間(Stop-the-World)を最小限に抑えるように設計されています。

runtime.MemStats

runtimeパッケージはGoランタイムに関する情報を提供し、MemStats構造体はGoプログラムのメモリ使用状況に関する詳細な統計を含んでいます。

  • MemStats.Sys: GoランタイムがOSから取得した総メモリ量(ヒープ、スタック、その他すべてを含む)を示します。これはプロセスが使用している仮想メモリの総量に近い値です。

ゴルーチン (Goroutine)

Go言語における軽量な実行スレッドです。数千、数万のゴルーチンを同時に実行することが容易であり、Goの並行処理の基盤となっています。各ゴルーチンは独自のスタックを持っています。

スプリットスタック (Split Stacks)

Go 1.2以前のGoランタイムで採用されていたスタック管理のメカニズムです。ゴルーチンのスタックは最初は小さく(数KB程度)割り当てられ、関数呼び出しが深くなるなどしてスタックが不足しそうになると、より大きなスタックセグメントを動的に割り当てて既存のスタックに連結(スプリット)します。これにより、多数のゴルーチンを起動してもメモリ消費を抑えることができます。スタックが縮小される際には、不要になったセグメントが解放されます。 Go 1.3以降では、より効率的な連続スタック(Contiguous Stacks)が導入され、スプリットスタックは廃止されましたが、このコミットが作成された2011年当時はスプリットスタックが主流でした。

gcgccgoコンパイラ

  • gc: Go言語の公式コンパイラであり、Goツールチェインの一部として提供されています。Go言語で書かれており、Goのランタイムと密接に連携して動作します。
  • gccgo: GCC(GNU Compiler Collection)のフロントエンドとして実装されたGoコンパイラです。C/C++など他の言語と同様にGCCの最適化パスを利用できます。gccgoはGoのランタイムを独自に実装しているため、gcコンパイラとは異なるメモリ管理やスタック管理の挙動を示すことがあります。このコミットの背景にある「スプリットスタックを使用しない場合のgccgo」という状況は、gccgoのランタイムがgcコンパイラのランタイムとは異なるスタック管理戦略を採用していたことを示唆しています。

技術的詳細

このコミットの技術的な核心は、メモリ使用量のテスト方法を「絶対的な総メモリ量」から「テスト実行中に追加で割り当てられたメモリ量」へと変更した点にあります。

元のテストでは、TestGcSys関数が終了した時点でのruntime.MemStats.Sysの値が、特定の閾値(10MB)を超えていないかをチェックしていました。しかし、前述の通りgccgoのような特定の環境では、テスト開始前から既に大きなスタックが割り当てられているため、この総メモリ量がGCの効率性とは無関係に高くなってしまう問題がありました。

新しいアプローチでは、以下の手順でメモリ使用量を測定します。

  1. テスト開始前に一度GCを実行し、runtime.MemStats.Sysの値を記録します(これをsys_beforeとします)。これは、テスト開始時点でのベースラインのメモリ使用量と見なされます。
  2. GCを動作させるための処理(workthegc()を100万回呼び出すループ)を実行します。
  3. 処理終了後に再度GCを実行し、runtime.MemStats.Sysの現在の値を取得します(これをsys_afterとします)。
  4. sys_afterからsys_beforeを減算することで、テスト実行中に追加で使用されたメモリ量(sys_after - sys_before)を算出します。
  5. この「追加で割り当てられたメモリ量」が新しい閾値(2MB)を超えていないかをチェックします。

この変更により、テストはシステム全体のメモリフットプリントではなく、GCがどれだけ効率的に不要なメモリを解放し、テスト中に余分なメモリが割り当てられなかったかを評価するようになります。これにより、異なるコンパイラやランタイムの実装による初期メモリ割り当ての差異に影響されにくく、より堅牢で正確なGCテストが可能になります。

コアとなるコードの変更箇所

src/pkg/runtime/gc_test.go ファイルの TestGcSys 関数が変更されています。

--- a/src/pkg/runtime/gc_test.go
+++ b/src/pkg/runtime/gc_test.go
@@ -6,16 +6,24 @@ import (
 )
 
 func TestGcSys(t *testing.T) {
+\truntime.GC()
+\truntime.UpdateMemStats()
+\tsys := runtime.MemStats.Sys
+\n \tfor i := 0; i < 1000000; i++ {\n \t\tworkthegc()\n \t}\n \n \t// Should only be using a few MB.
 \truntime.UpdateMemStats()\n-\tsys := runtime.MemStats.Sys
-\tt.Logf(\"using %d MB\", sys>>20)\n-\tif sys > 10e6 {\n-\t\tt.Fatalf(\"using too much memory: %d MB\", sys>>20)\n+\tif sys > runtime.MemStats.Sys {\n+\t\tsys = 0\n+\t} else {\
+\t\tsys = runtime.MemStats.Sys - sys
+\t}\
+\tt.Logf(\"used %d extra bytes\", sys)\
+\tif sys > 2<<20 {\
+\t\tt.Fatalf(\"using too much memory: %d bytes\", sys)\
 \t}\
 }\
 \n

コアとなるコードの解説

変更された TestGcSys 関数の主要な部分を解説します。

func TestGcSys(t *testing.T) {
	runtime.GC() // 明示的にGCを実行し、可能な限りメモリを解放する
	runtime.UpdateMemStats() // 最新のメモリ統計情報を更新
	sys := runtime.MemStats.Sys // テスト開始前の総メモリ使用量を記録

	for i := 0; i < 1000000; i++ {
		workthegc() // GCをトリガーする可能性のある処理を100万回実行
	}

	// Should only be using a few MB.
	runtime.UpdateMemStats() // 処理後の最新のメモリ統計情報を更新

	// テスト開始前のsys値と現在のruntime.MemStats.Sysを比較し、
	// テスト中に増えたメモリ量(extra bytes)を計算する。
	// もし現在のsys値がテスト開始前のsys値より小さい場合(メモリが解放された場合など)、
	// extra bytesは0とする。
	if sys > runtime.MemStats.Sys {
		sys = 0
	} else {
		sys = runtime.MemStats.Sys - sys
	}

	t.Logf("used %d extra bytes", sys) // 追加で使われたメモリ量をログに出力

	// 追加で使われたメモリ量が2MB (2 << 20 bytes) を超えていないかチェック
	if sys > 2<<20 {
		t.Fatalf("using too much memory: %d bytes", sys) // 超えていればテスト失敗
	}
}
  • runtime.GC(): この関数は、Goランタイムに明示的にガベージコレクションを実行するように要求します。テスト開始前にこれを呼び出すことで、初期状態のメモリをクリーンアップし、ベースラインのSys値が可能な限り低い状態になるようにします。
  • runtime.UpdateMemStats(): runtime.MemStats構造体の値を最新の状態に更新します。runtime.GC()の呼び出し後や、メモリ使用量をチェックする直前に呼び出すことで、正確な統計情報を取得できます。
  • sys := runtime.MemStats.Sys (初期化): テストが本格的に開始される前のシステム全体のメモリ使用量をsys変数に保存します。これが、後で「追加で割り当てられたメモリ」を計算するための基準点となります。
  • for i := 0; i < 1000000; i++ { workthegc() }: このループは、GCが動作するようなメモリ割り当てと解放を繰り返す処理(workthegc()関数の中身は不明ですが、GCを誘発するような処理と推測されます)を100万回実行します。これにより、GCが効率的に動作しているかを検証するための負荷をかけます。
  • if sys > runtime.MemStats.Sys { sys = 0 } else { sys = runtime.MemStats.Sys - sys }: この部分が変更の核心です。
    • ループ終了後、再度runtime.UpdateMemStats()を呼び出して最新のメモリ統計を取得します。
    • sys(テスト開始前のメモリ量)とruntime.MemStats.Sys(テスト終了後のメモリ量)を比較します。
    • もしテスト終了後のメモリ量が開始前より少ない場合(これはGCが非常に効率的に動作し、メモリを解放したことを意味します)、sys0に設定します。これは「追加で使われたメモリは実質ゼロ」と見なすためです。
    • そうでない場合(テスト終了後のメモリ量が増加している場合)、runtime.MemStats.Sys - sysを計算し、その差分をsysに再代入します。この差分が、テスト中に「追加で割り当てられ、かつ解放されなかったメモリ量」となります。
  • t.Logf("used %d extra bytes", sys): 計算された追加メモリ量をテストログに出力します。
  • if sys > 2<<20 { t.Fatalf("using too much memory: %d bytes", sys) }:
    • 2<<20はビットシフト演算で、2 * (2^20)、つまり2 * 1024 * 1024 = 2,097,152バイト、すなわち2MBを意味します。
    • 計算された追加メモリ量が2MBを超えている場合、テストは失敗し、エラーメッセージが出力されます。この閾値は、GCが適切に動作していれば、テスト中に余分なメモリが2MB以上残ることはないという期待に基づいています。

この修正により、TestGcSysは、特定のコンパイラ実装(gccgo)やスタック管理の挙動に起因する初期メモリ割り当ての大きさに影響されず、純粋にGCの効率性、すなわち「テスト実行中にどれだけ余分なメモリが残ったか」を評価できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のガベージコレクションに関する一般的な情報
  • runtime.MemStatsに関するGoのドキュメント
  • Go言語のスタック管理(スプリットスタック、連続スタック)に関する情報
  • gcgccgoコンパイラに関する情報

[インデックス 10769] ファイルの概要

このコミットは、Go言語のランタイムにおけるガベージコレクション(GC)のテスト gc_test.go の修正に関するものです。具体的には、TestGcSys 関数がメモリ使用量をテストする方法を変更し、総メモリ使用量ではなく、追加で割り当てられたメモリ空間をテストするように修正しています。

コミット

  • コミットハッシュ: 26239417bb0973c658a7d05e7c8b0b058562ccb8
  • 作者: Ian Lance Taylor iant@golang.org
  • コミット日時: 2011年12月13日 15:12:55 -0800

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/26239417bb0973c658a7d05e7c8b0b058562ccb8

元コミット内容

runtime: Make gc_test test extra allocated space, not total space.

Testing total space fails for gccgo when not using split
stacks, because then each goroutine has a large stack, and so
the total memory usage is large.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5487068

変更の背景

この変更の背景には、Go言語の異なるコンパイラ実装であるgccgoの特性が関係しています。Go言語には主に公式のgcコンパイラと、GCCフロントエンドとして実装されたgccgoコンパイラが存在します。

元のgc_test.goTestGcSysテストは、プログラム全体の総メモリ使用量(runtime.MemStats.Sys)をチェックしていました。しかし、前述の通りgccgoコンパイラを使用し、かつ「スプリットスタック(Split Stacks)」が有効になっていない環境では、各ゴルーチン(goroutine)が初期段階で大きなスタック空間を割り当ててしまうという問題がありました。

スプリットスタックは、Goのランタイムがゴルーチンのスタックサイズを動的に拡張・縮小するメカニズムです。これにより、初期のスタック割り当てを小さく保ち、必要に応じて拡張することでメモリ効率を高めます。スタックが縮小される際には、不要になったセグメントが解放されます。

しかし、gccgoがスプリットスタックをサポートしていない、または無効になっている場合、ゴルーチンごとに固定の大きなスタックが割り当てられるため、たとえGCが適切に動作していても、総メモリ使用量が非常に大きくなってしまい、テストが不必要に失敗する原因となっていました。Web検索の結果によると、gccgoはスプリットスタックをサポートしていますが、このコミットが作成された2011年当時は、特定の条件下(例えば、リンカの要件を満たさない場合や、-fno-split-stackフラグが使用された場合など)でスプリットスタックが有効にならない、あるいはその挙動がgcコンパイラと異なる可能性がありました。

このコミットは、このようなgccgo環境でのテストの誤検出を避けるため、テストの焦点を「総メモリ使用量」から「GCによって解放されずに残った追加の割り当てメモリ量」へと変更することを目的としています。これにより、コンパイラの実装やスタック管理の方式に依存せず、GCの効率性をより正確に評価できるようになります。

前提知識の解説

Go言語のガベージコレクション (GC)

Go言語は自動メモリ管理(ガベージコレクション)を採用しています。開発者は手動でメモリを解放する必要がありません。GoのGCは並行・世代別・マーク&スイープ方式をベースとしており、プログラムの実行と並行して動作し、アプリケーションの停止時間(Stop-the-World)を最小限に抑えるように設計されています。

runtime.MemStats

runtimeパッケージはGoランタイムに関する情報を提供し、MemStats構造体はGoプログラムのメモリ使用状況に関する詳細な統計を含んでいます。

  • MemStats.Sys: GoランタイムがOSから取得した総メモリ量(ヒープ、スタック、その他すべてを含む)を示します。これはプロセスが使用している仮想メモリの総量に近い値です。

ゴルーチン (Goroutine)

Go言語における軽量な実行スレッドです。数千、数万のゴルーチンを同時に実行することが容易であり、Goの並行処理の基盤となっています。各ゴルーチンは独自のスタックを持っています。

スプリットスタック (Split Stacks)

Go 1.2以前のGoランタイムで採用されていたスタック管理のメカニズムです。ゴルーチンのスタックは最初は小さく(数KB程度)割り当てられ、関数呼び出しが深くなるなどしてスタックが不足しそうになると、より大きなスタックセグメントを動的に割り当てて既存のスタックに連結(スプリット)します。これにより、多数のゴルーチンを起動してもメモリ消費を抑えることができます。スタックが縮小される際には、不要になったセグメントが解放されます。 Go 1.3以降では、より効率的な連続スタック(Contiguous Stacks)が導入され、スプリットスタックは廃止されましたが、このコミットが作成された2011年当時はスプリットスタックが主流でした。

gcgccgoコンパイラ

  • gc: Go言語の公式コンパイラであり、Goツールチェインの一部として提供されています。Go言語で書かれており、Goのランタイムと密接に連携して動作します。
  • gccgo: GCC(GNU Compiler Collection)のフロントエンドとして実装されたGoコンパイラです。C/C++など他の言語と同様にGCCの最適化パスを利用できます。gccgoはGoのランタイムを独自に実装しているため、gcコンパイラとは異なるメモリ管理やスタック管理の挙動を示すことがあります。Web検索の結果、gccgoもスプリットスタックをサポートしていることが確認されましたが、このコミットの背景にある「スプリットスタックを使用しない場合のgccgo」という状況は、当時のgccgoの特定のビルド設定や環境において、スプリットスタックが期待通りに機能しない、あるいはgcコンパイラとは異なるスタック割り当て戦略を採用していた可能性を示唆しています。

技術的詳細

このコミットの技術的な核心は、メモリ使用量のテスト方法を「絶対的な総メモリ量」から「テスト実行中に追加で割り当てられたメモリ量」へと変更した点にあります。

元のテストでは、TestGcSys関数が終了した時点でのruntime.MemStats.Sysの値が、特定の閾値(10MB)を超えていないかをチェックしていました。しかし、前述の通りgccgoのような特定の環境では、テスト開始前から既に大きなスタックが割り当てられているため、この総メモリ量がGCの効率性とは無関係に高くなってしまう問題がありました。

新しいアプローチでは、以下の手順でメモリ使用量を測定します。

  1. テスト開始前に一度GCを実行し、runtime.MemStats.Sysの値を記録します(これをsys_beforeとします)。これは、テスト開始時点でのベースラインのメモリ使用量と見なされます。
  2. GCを動作させるための処理(workthegc()を100万回呼び出すループ)を実行します。
  3. 処理終了後に再度GCを実行し、runtime.MemStats.Sysの現在の値を取得します(これをsys_afterとします)。
  4. sys_afterからsys_beforeを減算することで、テスト実行中に追加で使用されたメモリ量(sys_after - sys_before)を算出します。もしsys_aftersys_beforeより小さい場合は、追加メモリは0とみなされます。
  5. この「追加で割り当てられたメモリ量」が新しい閾値(2MB)を超えていないかをチェックします。

この変更により、テストはシステム全体のメモリフットプリントではなく、GCがどれだけ効率的に不要なメモリを解放し、テスト中に余分なメモリが割り当てられなかったかを評価するようになります。これにより、異なるコンパイラやランタイムの実装による初期メモリ割り当ての差異に影響されにくく、より堅牢で正確なGCテストが可能になります。

コアとなるコードの変更箇所

src/pkg/runtime/gc_test.go ファイルの TestGcSys 関数が変更されています。

--- a/src/pkg/runtime/gc_test.go
+++ b/src/pkg/runtime/gc_test.go
@@ -6,16 +6,24 @@ import (
 )
 
 func TestGcSys(t *testing.T) {
+\truntime.GC()
+\truntime.UpdateMemStats()
+\tsys := runtime.MemStats.Sys
+\n \tfor i := 0; i < 1000000; i++ {\n \t\tworkthegc()\n \t}\n \n \t// Should only be using a few MB.
 \truntime.UpdateMemStats()\n-\tsys := runtime.MemStats.Sys
-\tt.Logf(\"using %d MB\", sys>>20)\n-\tif sys > 10e6 {\n-\t\tt.Fatalf(\"using too much memory: %d MB\", sys>>20)\n+\tif sys > runtime.MemStats.Sys {\n+\t\tsys = 0\n+\t} else {\
+\t\tsys = runtime.MemStats.Sys - sys
+\t}\
+\tt.Logf(\"used %d extra bytes\", sys)\
+\tif sys > 2<<20 {\
+\t\tt.Fatalf(\"using too much memory: %d bytes\", sys)\
 \t}\
 }\
 \n

コアとなるコードの解説

変更された TestGcSys 関数の主要な部分を解説します。

func TestGcSys(t *testing.T) {
	runtime.GC() // 明示的にGCを実行し、可能な限りメモリを解放する
	runtime.UpdateMemStats() // 最新のメモリ統計情報を更新
	sys := runtime.MemStats.Sys // テスト開始前の総メモリ使用量を記録

	for i := 0; i < 1000000; i++ {
		workthegc() // GCをトリガーする可能性のある処理を100万回実行
	}

	// Should only be using a few MB.
	runtime.UpdateMemStats() // 処理後の最新のメモリ統計情報を更新

	// テスト開始前のsys値と現在のruntime.MemStats.Sysを比較し、
	// テスト中に増えたメモリ量(extra bytes)を計算する。
	// もし現在のsys値がテスト開始前のsys値より小さい場合(メモリが解放された場合など)、
	// extra bytesは0とする。
	if sys > runtime.MemStats.Sys {
		sys = 0
	} else {
		sys = runtime.MemStats.Sys - sys
	}

	t.Logf("used %d extra bytes", sys) // 追加で使われたメモリ量をログに出力

	// 追加で使われたメモリ量が2MB (2 << 20 bytes) を超えていないかチェック
	if sys > 2<<20 {
		t.Fatalf("using too much memory: %d bytes", sys) // 超えていればテスト失敗
	}
}
  • runtime.GC(): この関数は、Goランタイムに明示的にガベージコレクションを実行するように要求します。テスト開始前にこれを呼び出すことで、初期状態のメモリをクリーンアップし、ベースラインのSys値が可能な限り低い状態になるようにします。
  • runtime.UpdateMemStats(): runtime.MemStats構造体の値を最新の状態に更新します。runtime.GC()の呼び出し後や、メモリ使用量をチェックする直前に呼び出すことで、正確な統計情報を取得できます。
  • sys := runtime.MemStats.Sys (初期化): テストが本格的に開始される前のシステム全体のメモリ使用量をsys変数に保存します。これが、後で「追加で割り当てられたメモリ」を計算するための基準点となります。
  • for i := 0; i < 1000000; i++ { workthegc() }: このループは、GCが動作するようなメモリ割り当てと解放を繰り返す処理(workthegc()関数の中身は不明ですが、GCを誘発するような処理と推測されます)を100万回実行します。これにより、GCが効率的に動作しているかを検証するための負荷をかけます。
  • if sys > runtime.MemStats.Sys { sys = 0 } else { sys = runtime.MemStats.Sys - sys }: この部分が変更の核心です。
    • ループ終了後、再度runtime.UpdateMemStats()を呼び出して最新のメモリ統計を取得します。
    • sys(テスト開始前のメモリ量)とruntime.MemStats.Sys(テスト終了後のメモリ量)を比較します。
    • もしテスト終了後のメモリ量が開始前より少ない場合(これはGCが非常に効率的に動作し、メモリを解放したことを意味します)、sys0に設定します。これは「追加で使われたメモリは実質ゼロ」と見なすためです。
    • そうでない場合(テスト終了後のメモリ量が増加している場合)、runtime.MemStats.Sys - sysを計算し、その差分をsysに再代入します。この差分が、テスト中に「追加で割り当てられ、かつ解放されなかったメモリ量」となります。
  • t.Logf("used %d extra bytes", sys): 計算された追加メモリ量をテストログに出力します。
  • if sys > 2<<20 { t.Fatalf("using too much memory: %d bytes", sys) }:
    • 2<<20はビットシフト演算で、2 * (2^20)、つまり2 * 1024 * 1024 = 2,097,152バイト、すなわち2MBを意味します。
    • 計算された追加メモリ量が2MBを超えている場合、テストは失敗し、エラーメッセージが出力されます。この閾値は、GCが適切に動作していれば、テスト中に余分なメモリが2MB以上残ることはないという期待に基づいています。

この修正により、TestGcSysは、特定のコンパイラ実装(gccgo)やスタック管理の挙動に起因する初期メモリ割り当ての大きさに影響されず、純粋にGCの効率性、すなわち「テスト実行中にどれだけ余分なメモリが残ったか」を評価できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のガベージコレクションに関する一般的な情報
  • runtime.MemStatsに関するGoのドキュメント
  • Go言語のスタック管理(スプリットスタック、連続スタック)に関する情報
  • gcgccgoコンパイラに関する情報
  • Web検索: "gccgo split stacks" (https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFG61A7i7X1ls3KuvChImJbiKlkG6BqxM2ix6BPWk94kSEPowwtEE-VzzSG72CCGx3C9aIoDzblKXT8Ay0cr1lhSS5aPjILjUxbByDftoad3IiIoS8j7Qq20bStxj4Bgg==)