Bahasa Pengembangan Perangkat Lunak Komputasi Ilmiah Paralel?


18

Saya ingin mengembangkan perangkat lunak komputasi paralel ilmiah dari awal. Saya ingin beberapa pemikiran tentang bahasa mana untuk memulai. Program ini melibatkan membaca / menulis data ke file txt dan melakukan perhitungan berat secara paralel, dengan banyak faktorisasi LU dan penggunaan pemecah linier yang jarang. Solusi kandidat yang saya pikirkan adalah Fortran 2003/2008 dengan OpenMP atau co-array, C ++ dengan openmp cilk + atau TBB, python. Saran lainnya, yang terdokumentasi, dipersilakan! Saya tahu betul C, Fortran dan Jawa (dalam urutan itu). Saya telah melakukan beberapa scripting dengan python tetapi hal-hal dasar.

Saya tahu fortran sangat cepat, tetapi sulit dipertahankan dan diparalelkan. C ++ dikatakan lambat kecuali jika Anda menggunakan perpustakaan eksternal dll. Python saya suka, tetapi apakah realistis untuk menulis skala penuh, perangkat lunak tingkat industri?

Perangkat lunak ini harus mampu menangani data dalam jumlah besar dan efektif dengan perhitungan ilmiah. Kinerja adalah esensi.

Untuk latar belakang, saya sudah memiliki perangkat lunak yang berfungsi ditulis dalam Fortran. Banyak orang terlibat dalam pengembangan selama bertahun-tahun dan kodenya benar-benar kotor. Mempertahankan dan memparalelkan kode telah membuktikan mimpi buruk dan saya sedang memikirkan alternatif.

Petros


5
Sebagai C ++ wink, saya tidak akan memanggil Fortran sulit untuk dipertahankan. Maintainability terkait dengan praktik yang baik untuk sebagian besar, bukan pilihan bahasa. Kelambatan C ++ oversold. Juga, saya akan merekomendasikan Anda menambah posting ini untuk menjelaskan ukuran data Anda dan persyaratan waktu penyelesaian. Saya telah melihat "besar" bervariasi pada 9 atau 10 kali lipat tergantung pada siapa saya berbicara.
Bill Barth

@BillBarth Masalah dengan kode Fortran yang ada adalah bahwa tiga orang terlibat menggunakan praktik yang berbeda. Saya berasal dari latar belakang C, seorang lelaki dari latar belakang F77 dan seorang lelaki lain dari Matlab. Data tidak dapat dialokasikan dan berukuran untuk sistem ukuran terbesar (saya terlibat belakangan ini). Kode mampu mensimulasikan sistem dengan persamaan diferensial aljabar 72000 diferensial dan 74000 selama cakrawala waktu 240 di 350-an (waktu berlalu). Saya mengurangi itu menjadi 170-an dengan menggunakan OpenMP untuk memparalelkan. Sekarang saya perlu menjalankan beberapa case secara paralel (untuk menyapu untuk pemeriksaan keamanan).
electrique

4
@BillBarth terlalu rendah hati dalam menjual keterampilan C ++, tetapi dia juga terlalu murah hati dalam pernyataannya bahwa "kelambatan C ++ terlalu banyak dijual". Ada sejumlah utas C ++ vs Fortran di scicomp.stackexchange.com yang telah membahas pertanyaan ini dan kesimpulan umum adalah bahwa itu tidak benar lagi karena C ++ lebih lambat daripada Fortran + lebih lambat dari Fortran untuk hampir semua kasus. Saya pribadi berpikir bahwa hari ini dapat dianggap sebagai mitos urban. Apa yang sangat benar adalah bahwa jika Anda mempertimbangkan pemeliharaan kode, maka Fortran tidak berjalan dengan baik hari ini.
Wolfgang Bangerth

2
@BillBarth dan yang lainnya, jika Anda ingin terus mendiskusikan manfaat umum dari Fortran, C ++, dan bahasa lainnya, silakan bawa ke ruang obrolan scicomp dan @ siapa pun yang ingin Anda atasi secara khusus.
Aron Ahmadia

1
@AronAhmadia: ah, ayolah, saya punya banyak hal untuk dikatakan pada Jed ;-) (Jed: lain waktu. Dalam kasus kami, tidak ada STL untuk matriks yang jarang, tetapi banyak di struktur data mesh adaptif.)
Wolfgang Bangerth

Jawaban:


19

Biarkan saya mencoba dan menjabarkan persyaratan Anda:

  • Maintabilitas
  • Membaca / menulis data teks
  • Antarmuka / kemampuan yang kuat untuk faktorisasi LU
  • Pemecah linier yang jarang
  • Kinerja dan skalabilitas untuk data besar

Dari daftar ini, saya akan mempertimbangkan bahasa-bahasa berikut:

C, C ++, Fortran, Python, MATLAB, Java

Julia adalah bahasa baru yang menjanjikan, tetapi komunitas masih membentuk sekitarnya dan belum digunakan dalam kode baru utama.

Membaca / menulis data teks

Ini mudah dilakukan dengan benar dalam bahasa pemrograman apa pun. Pastikan Anda melindungi dan menyatukan akses I / O Anda dengan tepat, dan Anda akan mendapatkan kinerja yang baik dari salah satu bahasa yang harus Anda pertimbangkan. Hindari streaming objek di C ++ kecuali Anda tahu cara menggunakannya secara performant.

Antarmuka / kemampuan yang kuat untuk faktorisasi LU

Jika Anda melakukan padat faktorisasi LU, Anda akan ingin menggunakan LAPACK, atau ScaLAPACK / Elemental untuk fungsi paralel. LAPACK dan ScaLAPACK ditulis dalam Fortran, Elemental ditulis dalam C ++. Ketiga perpustakaan tersebut berkinerja baik, didukung dan didokumentasikan dengan baik. Anda dapat antarmuka ke dalamnya dari salah satu bahasa yang harus Anda pertimbangkan.

Pemecah linier yang jarang

Solver linier jarang yang tersedia secara bebas hampir semuanya tersedia melalui PETSc , ditulis dalam C, yang didokumentasikan dan didukung dengan baik. Anda dapat antarmuka ke dalam PETSc dari salah satu bahasa yang harus Anda pertimbangkan.

Kinerja dan skalabilitas untuk data besar

Satu-satunya paradigma pemrograman paralel yang Anda sebutkan adalah berbasis memori bersama, yang berarti Anda tidak mempertimbangkan pendekatan komputasi distribusi memori berbasis MPI (message-passing). Dalam pengalaman saya, jauh lebih mudah untuk menulis kode yang skala jauh melampaui selusin inti menggunakan solusi memori terdistribusi. Hampir semua "cluster" Universitas berbasis MPI hari ini, mesin memori bersama yang besar harganya mahal, dan jarang terjadi. Anda harus mempertimbangkan MPI untuk pendekatan Anda, tetapi saran saya akan berlaku terlepas dari paradigma pemrograman yang Anda pilih.

Sehubungan dengan kinerja on-node, jika Anda menulis rutinitas numerik sendiri, paling mudah untuk mendapatkan kinerja serial yang baik di Fortran. Jika Anda memiliki sedikit pengalaman dalam C, C ++, atau Python, Anda bisa mendapatkan kinerja yang sangat sebanding (C dan C ++ mati-bahkan dengan Fortran, Python dan MATLAB memiliki sekitar 25% waktu overhead tanpa banyak usaha). MATLAB melakukan ini melalui kompiler JIT dan ekspresifitas aljabar linier yang sangat baik. Anda mungkin perlu menggunakan kernel Cython, numpy, numexpr, atau embed numerik untuk mendapatkan kinerja yang diklaim dari Python. Saya tidak dapat mengomentari kinerja Java, karena saya tidak tahu bahasanya dengan baik, tetapi saya curiga bahasa ini tidak jauh dari Python jika ditulis oleh seorang ahli.

Catatan tentang antarmuka

Saya harap saya telah meyakinkan Anda bahwa Anda akan dapat melakukan semua yang Anda inginkan dalam bahasa pemrograman yang Anda pertimbangkan. Jika Anda menggunakan Java, antarmuka C akan sedikit menantang. Python memiliki dukungan antarmuka C dan Fortran yang sangat baik melalui ctypes, Cython, dan f2py. LAPACK sudah dibungkus dan tersedia melalui scipy. MATLAB memiliki semua fungsionalitas yang Anda butuhkan di pustaka aslinya, tetapi tidak mudah diskalakan atau sangat mudah dijalankan pada cluster. Java dapat mendukung antarmuka C dan Fortran dengan JNI , tetapi tidak umum ditemukan pada cluster dan perangkat lunak paralel untuk komputasi ilmiah.

Maintabilitas

Banyak dari ini akan turun ke selera pribadi, tetapi konsensus umum tentang rawatan adalah bahwa Anda ingin meminimalkan jumlah baris kode dalam perangkat lunak Anda, menulis kode modular dengan antarmuka yang terdefinisi dengan baik, dan untuk perangkat lunak komputasi, menyediakan tes yang memverifikasi kebenaran dan fungsionalitas implementasi.

Rekomendasi

Saya pribadi telah memiliki banyak keberuntungan dengan Python dan saya merekomendasikannya untuk banyak proyek komputasi. Saya pikir Anda harus sangat mempertimbangkannya untuk proyek Anda. Python dan MATLAB mungkin adalah bahasa yang paling ekspresif yang tersedia untuk komputasi ilmiah. Anda dapat dengan mudah antarmuka Python ke bahasa pemrograman lain, Anda dapat menggunakan f2py untuk membungkus implementasi Fortran Anda saat ini dan menulis ulang sepotong demi sepotong bagian mana pun yang Anda inginkan dalam Python sambil memverifikasi bahwa Anda mempertahankan fungsionalitas. Pada saat ini, saya akan merekomendasikan kombinasi implementasi Python 2.7 resmi dengan scipy . Anda dapat dengan mudah memulai dengan tumpukan ini dari Enthought Python Distribution yang tersedia secara bebas .

Anda juga bisa melakukan sebagian besar ini dalam C, C ++, atau Fortran. C dan C ++ adalah bahasa yang sangat menarik untuk pengembang profesional dengan banyak pengalaman, tetapi sering membuat pengembang baru dan dalam hal ini mungkin bukan ide yang bagus untuk kode yang lebih akademis. Fortran dan MATLAB populer dalam perhitungan akademis, tetapi lemah pada struktur data canggih dan penawaran ekspresif Python (pikirkan objek dict Python, misalnya).

Pertanyaan-pertanyaan Terkait:


1
Jawaban inklusif yang didokumentasikan dengan sangat baik. Di bawah Fortran saya menggunakan Lapack banyak. Saya akan melihat python dan mencoba untuk membungkus kode Fortran saya untuk memulai dan perlahan-lahan pindah ke Python. Satu-satunya hal yang membuat saya takut adalah 25% waktu overhead yang mungkin saya miliki. Tetapi jika itu datang dengan manfaat kode yang lebih ekspresif dan penanganan komputasi paralel yang lebih baik, saya akan melakukannya. Saya menyebutkan memori bersama hanya karena perangkat lunak saat ini berjalan secara interaktif (membuat perubahan pada data dan menjalankan kembali) pada 2,4,8,24,48-core komputer memori bersama para peneliti di Uni di bawah Windows dan Linux.
electrique

3
Saya tidak tahu bagaimana Anda bisa mengklaim 25% overhead untuk kernel numerik yang ditulis dengan Python. Kernel numerik Python murni sering urutan 100x lebih lambat dari C. Numpy dan numexpr dapat melakukan pekerjaan yang layak dengan ekspresi tertentu, tetapi itu jarang menulis kernel numerik baru di Python. Cython dapat membuat beberapa hal cepat, tetapi biasanya tidak dalam 25% dari C. Python adalah bahasa "perekat" yang bagus, tapi saya pikir Aron menjualnya secara berlebihan sebagai solusi tujuan umum untuk tugas yang sensitif terhadap kinerja.
Jed Brown

I / O adalah titik lemah Fortran, karena Fortran membutuhkan banyak struktur di I / O. Pengalaman bekas saya dari berbicara dengan rekan kerja di lab saya yang bekerja dengan Cython cocok dengan apa yang dikatakan Jed tentang Cython; setidaknya salah satu dari mereka menulis tanda tangan C untuk menggantikan Cython untuk tugas-tugas intensif kinerja, dan kemudian saya percaya kinerja Python memanggil kode C yang dihasilkan lebih dekat dengan klaim Aron. Juga, jika Anda akan menyebutkan PETSc dan Python, Anda mungkin juga menyebutkan petsc4py. Terakhir saya melihat (ini beberapa tahun yang lalu), tidak ada antarmuka MPI yang baik untuk Java. Apakah itu sudah berubah?
Geoff Oxberry

@ GeoffOxberry: binding Java MPI ada tetapi belum diperbarui dalam hampir satu dekade. Saya menganggap status mereka meragukan. Fortran memiliki banyak opsi I / O yang dapat dibuat dengan sangat cepat. Saya akan merekomendasikan menjelajahi HDF5 Paralel (dan HDF5, umumnya). Jika I / O benar-benar dominan (lebih dari 50% waktu berjalan), langkah-langkah yang lebih serius mungkin dilakukan, tetapi jika tidak, kualitas dan portabilitas dari dan antarmuka seperti HDF mungkin sepadan.
Bill Barth

@ BillBarth: Saya harus memeriksanya. Komentar saya tentang Fortran I / O berasal dari sudut pandang seseorang yang pernah merekomendasikan agar saya menulis parser file input di Fortran. Itu mungkin, dengan menegakkan banyak struktur, tapi saya belum melihat dengan mudah dan banyak digunakan parser regex atau parser XML parser di Fortran (untuk memberikan beberapa contoh). Ada alasan bagus untuk itu: kita satu-satunya orang yang menggunakan Fortran lagi. Mungkin kita sedang memikirkan berbagai kasus penggunaan.
Geoff Oxberry

2

Selain jawaban Aron yang sangat komprehensif, saya akan melihat berbagai utas tentang scicomp.stackexchange yang membahas pertanyaan tentang bahasa pemrograman mana yang akan diambil - baik mengenai kecepatan program maupun pertanyaan tentang seberapa mudah atau sulitnya itu untuk menulis dan memelihara perangkat lunak dalam bahasa-bahasa ini.

Yang mengatakan, selain apa yang telah ditulis di sana, izinkan saya membuat beberapa pengamatan:

(i) Anda memasukkan Fortran co-array dalam daftar Anda. Setahu saya, jumlah kompiler yang benar-benar mendukungnya sangat kecil - dan saya, sebenarnya, nol. Kompiler Fortran yang paling banyak tersedia adalah GNU gfortran, dan sementara sumber pengembangan saat ini menguraikan subset dari co-array, saya percaya bahwa itu sebenarnya tidak mendukung semua itu (yaitu, ia menerima sintaks tetapi tidak mengimplementasikan semantik) . Ini tentu saja merupakan pengamatan umum tentang standar Fortran yang lebih baru: bahwa kelambatan yang digunakan penyusun sebenarnya mendukung standar baru diukur dalam beberapa tahun - penyusun hanya menerapkan sepenuhnya Fortran 2003 dalam beberapa tahun terakhir, dan hanya sebagian mendukung Fortran 2008. Ini seharusnya tidak menghentikan Anda dari menggunakannya jika Anda memiliki kompiler yang mendukung apa yang Anda gunakan,

(ii) Hal yang sama juga berlaku untuk C ++ / Cilk +: Ya, Intel mengembangkan ini pada cabang GCC tetapi tidak tersedia di rilis GCC mana pun dan, sepertinya, tidak akan untuk sementara waktu. Anda bisa berharap untuk mengambil 2-3 tahun lagi setidaknya sampai Anda akan menemukan Cilk + dengan versi GCC diinstal pada mesin linux yang khas.

(iii) C ++ / TBB adalah cerita yang berbeda: TBB telah ada untuk sementara waktu, memiliki antarmuka yang sangat stabil dan dapat dikompilasi dengan sebagian besar kompiler C ++ yang telah ada selama beberapa tahun terakhir (di linux dan juga di windows) . Kami telah menggunakannya dalam kesepakatan. Saya selama beberapa tahun sudah dengan hasil yang baik. Ada juga buku yang sangat bagus.

(iv) Saya memiliki pendapat sendiri tentang OpenMP, yaitu bahwa ini merupakan solusi dalam mencari masalah. Ini bekerja dengan baik untuk memparalelkan loop dalam yang mungkin menarik jika Anda memiliki struktur data yang sangat teratur. Tetapi jarang apa yang ingin Anda lakukan jika Anda perlu memparalelkan sesuatu - karena apa yang sebenarnya ingin Anda lakukan adalah memparalelkan loop luar . Dan untuk itu, solusi seperti TBB adalah solusi yang jauh lebih baik karena mereka menggunakan mekanisme bahasa pemrograman daripada mencoba menggambarkan apa yang terjadi di luar bahasa (melalui #pragmas) dan sedemikian rupa sehingga Anda tidak memiliki akses ke ulir thread , indikator status hasil, dll, dari dalam program Anda.

(v) Jika Anda eksperimental, Anda mungkin juga melihat bahasa pemrograman baru yang dirancang untuk pemrograman paralel dan, khususnya, untuk tugas-tugas seperti yang Anda jelaskan. Pada dasarnya ada dua yang akan saya lihat: X10 dan Chapel . Saya telah melihat tutorial yang bagus tentang Chapel, dan tampaknya dirancang dengan baik, meskipun keduanya tentu saja hari ini adalah solusi picik juga.


Sebagai catatan, Intel mengklaim memiliki co-array paralel (memori terdistribusi) yang diparalel ke dalam kompiler mereka saat ini. Kami sedang mencari di TACC, tapi saya belum melaporkan. Cray juga memiliki implementasi dalam kompiler mereka, tetapi ini hanya tersedia pada sejumlah kecil mesin integer di seluruh dunia. Saya tidak berpikir ada yang mengimplementasikan standar penuh Fortran 2008 sehubungan dengan co-array, namun, ada lebih dari dukungan yang baru lahir dalam beberapa kompiler. Cilk +, tentu saja, juga tersedia dengan kompiler Intel, tetapi bergantung pada mungkin belum bijaksana.
Bill Barth

Standar Fortran 2008 tidak disetujui hingga akhir 2010 sehingga akan diperlukan beberapa tahun sebelum CAF tersedia secara luas. G95 sebenarnya memiliki implementasi (tidak bebas) tetapi tidak dikembangkan lagi (pengembang telah bergabung dengan PathScale).
stali

Sebagian besar G95 akhirnya berakhir di Gfortran tetapi mungkin CAF bukan bagian dari itu.
Wolfgang Bangerth

Saya percaya kompiler Intel memberikan dukungan co-array yang baik. Mereka telah membangunnya menggunakan mpiexec. Itu tidak akan menjadi pilihan pertama saya. Yang menyenangkan adalah implementasi yang sama dapat berjalan pada memori yang dibagikan dan didistribusikan (saya menjalankan beberapa tes). Dengan prosesor memori bersama opteron mencapai 60-core dengan harga yang sangat wajar, saya ingin melihat opsi memori bersama saya terlebih dahulu.
electrique

2

Secara umum, jika Anda benar-benar serius tentang proyek perangkat lunak ini, saya sarankan menulis ulang lengkap dalam bahasa apa pun yang Anda sendiri merasa paling nyaman dengannya. Sepertinya Anda akan melakukan pekerjaan sendiri, dan karena itu Anda akan mendapatkan hasil terbaik dalam bahasa yang paling Anda sukai di rumah.

Lebih khusus lagi, mengenai paralelisme, saya mendorong Anda untuk mencoba berpikir sedikit di luar kebiasaan. OpenMP memiliki kelebihan, tetapi terjebak dalam pola pikir mengambil kode sekuensial dan menampar paralelisme di sana-sini. Hal yang sama berlaku untuk Intels TBB.

Cilk jelas merupakan langkah ke arah yang benar, yaitu memaksa Anda untuk memikirkan kembali masalah / solusi Anda dalam pengaturan paralel yang inheren. Namun, yang saya tidak suka tentang itu adalah bahwa itu adalah bahasa lain . Juga, karena hanya dapat secara kasar menyimpulkan hubungan antara tugas paralel, penjadwal bisa sangat konservatif dan mungkin tidak skala dengan baik untuk masalah tertentu.

Namun, kabar baiknya adalah, bahwa, sekali lagi, jika Anda serius tentang implementasi Anda, Anda dapat melakukan apa yang dilakukan Cilk, misalnya menulis ulang masalah Anda sebagai serangkaian tugas yang saling tergantung dan mendistribusikannya ke sejumlah prosesor / core, semuanya Anda gunakan menggunakan pthreads atau menyalahgunakan OpenMP untuk memunculkan proses. Contoh yang bagus tentang bagaimana hal ini dapat dilakukan adalah penjadwal QUARK yang digunakan di perpustakaan PLASMA . Perbandingan bagus antara kinerjanya vs Cilk diberikan di sini .


Saya akan melihat tautan yang disarankan. Makalah perbandingan sangat bagus! Terima kasih! Saya sudah memikirkan tentang pthreads tetapi saya ingin program ini menjadi cross-platform. Dari yang saya tahu pthreads memiliki masalah di bawah windows (salah?).
electrique

@ p3tris: "p" dalam pthreads adalah untuk POSIX, jadi ini se portable mungkin. Ada beberapa implementasi Windows yang sesuai seperti pthreads-win32atau dalam cygwinproyek.
Pedro

Berdasarkan stackoverflow.com/q/2797690/801468, saya melihat ada banyak hal yang perlu diselesaikan untuk menggunakannya. Mengingat saya bukan seorang programmer, saya lebih suka menggunakan sesuatu yang lebih teruji.
electrique

2

Ada sedikit diskusi tentang coarray fortran di komentar di atas. Pada saat ini, dan setahu saya, dukungan coarray dalam kompiler kira-kira sebagai berikut:

  • Cray memiliki kompiler yang mendukung setidaknya fitur coarray dasar. Saya sudah menggunakannya untuk menulis kode yang dimaksudkan untuk menjadi "mendidik", tetapi saya akan mengatakan bahwa Anda dapat menulis kode nyata di coarray fortran. Sintaks dan konsep sebagian besar jauh lebih sederhana daripada MPI, tetapi seperti biasa, ada jebakan lotsa, dan jebakan berbeda dari MPI.
  • Intel fortran memiliki dukungan coarray yang dibangun di atas perpustakaan MPI mereka. Seharusnya ini membatasi kinerja puncak teoretis mereka, tetapi saya belum melihat metrik.
  • Gfortran mendukung coarrays, tetapi hanya untuk satu gambar (atau peringkat tunggal, dalam bahasa MPI). Oleh karena itu, tidak ada paralelisasi nyata yang tersedia sampai gfortran 4.8 atau 4.9 keluar.

Secara umum, saya akan berhati-hati jika memulai kode berbasis coarray. Sintaksnya sederhana dan jauh lebih nyaman daripada Fortran / C / C ++ dengan MPI, tapi kemudian, hanya saja tidak memiliki fitur lengkap. Misalnya, MPI mendukung banyak operasi pengurangan, dll. Yang mungkin sangat nyaman bagi Anda. Itu akan sangat tergantung pada kebutuhan Anda akan banyak komunikasi. Jika Anda ingin contoh, beri tahu saya dan saya dapat memberikan beberapa kepada Anda, jika saya dapat menggali file.


Ya, informasi lebih lanjut tentang kesiapan coarray Fortran untuk masalah semacam ini tentu akan sangat membantu. Selamat datang di scicomp!
Aron Ahmadia

1

Lihatlah Spark itu kerangka kerja terdistribusi untuk komputasi dalam memori yang mengambil keuntungan dari pemrograman fungsional. Struktur sebuah program di Spark sangat berbeda jika dibandingkan dengan MPI, pada dasarnya Anda menulis kode seperti untuk komputer tunggal, yang secara otomatis didistribusikan sebagai fungsi ke data yang berada di memori. Ini mendukung Scala, Java dan Python.

Regresi Logistik (scala):

//load data to distributed memory
val points = spark.textFile(...).map(parsePoint).cache()
var w = Vector.random(D) // current separating plane
for (i <- 1 to ITERATIONS) {
  val gradient = points.map(p =>
    (1 / (1 + exp(-p.y*(w dot p.x))) - 1) * p.y * p.x
  ).reduce(_ + _)
  w -= gradient
}
println("Final separating plane: " + w)

Ada ekstensi untuk disebut MLib (Perpustakaan Pembelajaran Mesin) yang menggunakan perpustakaan Fortran untuk beberapa perhitungan tingkat rendah (untuk Python saya kira numpy digunakan). Jadi, idenya sederhana, berkonsentrasilah pada algoritma Anda dan tinggalkan optimisasi ke level yang lebih rendah (urutan pemrosesan, distribusi data, dll.).

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.