কেন আমার কোড এত ধীর? py-spy ডেটা সায়েন্সের দিকে পাইথন প্রোফাইলিংয়ের একটি গাইড

কেন আমার কোড এত ধীর? py-spy ডেটা সায়েন্সের দিকে পাইথন প্রোফাইলিংয়ের একটি গাইড


ডেটা সায়েন্স কোড ডিবাগ করার হতাশাজনক সমস্যাগুলি সিনট্যাক্স ত্রুটি বা যৌক্তিক ভুল নয়। বরং, এগুলি কোড থেকে আসে যা ঠিক যা করার কথা তাই করে, কিন্তু করতে খুব বেশি সময় লাগে।

কার্যকরী কিন্তু অদক্ষ কোড ডেটা সায়েন্স ওয়ার্কফ্লোতে একটি বড় বাধা হতে পারে। এই নিবন্ধে, আমি একটি সংক্ষিপ্ত ভূমিকা এবং বর্ণনা প্রদান করবে py-spyআপনার পাইথন কোড প্রোফাইল করার জন্য ডিজাইন করা একটি শক্তিশালী টুল। এটি ঠিক কোথায় আপনার প্রোগ্রামটি সবচেয়ে বেশি সময় ব্যয় করছে তা চিহ্নিত করতে পারে যাতে অদক্ষতা চিহ্নিত করা যায় এবং ঠিক করা যায়।

উদাহরণ সমস্যা

এর জন্য কিছু কোড লিখতে একটি সহজ গবেষণা প্রশ্ন সেট করা যাক:

“মার্কিন রাজ্য এবং অঞ্চলগুলির মধ্যে যাওয়া সমস্ত ফ্লাইটের জন্য, কোন প্রস্থান বিমানবন্দরে গড়ে সবচেয়ে দীর্ঘ ফ্লাইট রয়েছে?”

ব্যুরো অফ ট্রান্সপোর্টেশন স্ট্যাটিস্টিকস (BTS) থেকে প্রাপ্ত ডেটা ব্যবহার করে এই গবেষণা প্রশ্নের উত্তর দেওয়ার জন্য নীচে একটি সাধারণ পাইথন স্ক্রিপ্ট রয়েছে। ডেটাসেটে মার্কিন যুক্তরাষ্ট্রের রাজ্য এবং অঞ্চলগুলির মধ্যে 2025 সালের জানুয়ারি থেকে জুনের মধ্যে প্রতিটি ফ্লাইটের ডেটা রয়েছে, সাথে মূল এবং গন্তব্য বিমানবন্দরের তথ্য রয়েছে৷ এটি প্রায় 3.5 মিলিয়ন সারি।

এটি প্রতিটি ফ্লাইটের জন্য হ্যাভারসাইন দূরত্ব – একটি গোলকের দুটি বিন্দুর মধ্যে সবচেয়ে কম দূরত্ব – গণনা করে। তারপরে, এটি গড় দূরত্ব খুঁজে বের করার জন্য প্রস্থান বিমানবন্দর দ্বারা ফলাফলগুলিকে গোষ্ঠীভুক্ত করে এবং শীর্ষ পাঁচটি প্রতিবেদন করে।

import pandas as pd  
import math  
import time  
  
  
def haversine(lat_1, lon_1, lat_2, lon_2):  
    """Calculate the Haversine Distance between two latitude and longitude points"""  
    lat_1_rad = math.radians(lat_1)  
    lon_1_rad = math.radians(lon_1)  
    lat_2_rad = math.radians(lat_2)  
    lon_2_rad = math.radians(lon_2)  
  
    delta_lat = lat_2_rad - lat_1_rad  
    delta_lon = lon_2_rad - lon_1_rad  
  
    R = 6371  # Radius of the earth in km  
  
    return 2*R*math.asin(math.sqrt(math.sin(delta_lat/2)**2 + math.cos(lat_1_rad)*math.cos(lat_2_rad)*(math.sin(delta_lon/2))**2))  
  
  
if __name__ == '__main__':  
    # Load in flight data to a dataframe  
    flight_data_file = r"./data/2025_flight_data.csv"  
    flights_df = pd.read_csv(flight_data_file)  
  
    # Start timer to see how long analysis takes  
    start = time.time()  
  
    # Calculate the haversine distance between each flight's start and end airport  
    haversine_dists = []  
    for i, row in flights_df.iterrows():  
        haversine_dists.append(haversine(lat_1=row["LATITUDE_ORIGIN"],  
                                         lon_1=row["LONGITUDE_ORIGIN"],  
                                         lat_2=row["LATITUDE_DEST"],  
                                         lon_2=row["LONGITUDE_DEST"]))  
  
    flights_df["Distance"] = haversine_dists  
  
    # Get result by grouping by origin airport, taking the average flight distance and      printing the top 5  
    result = (  
        flights_df  
        .groupby('DISPLAY_AIRPORT_NAME_ORIGIN').agg(avg_dist=('Distance', 'mean'))  
        .sort_values('avg_dist', ascending=False)  
    )  
  
    print(result.head(5))  
  
    # End timer and print analysis time  
    end = time.time()  
    print(f"Took {end - start} s")

এই কোড চালানো নিম্নলিখিত আউটপুট দেয়:

                                        avg_dist
DISPLAY_AIRPORT_NAME_ORIGIN                     
Pago Pago International              4202.493567
Guam International                   3142.363005
Luis Munoz Marin International       2386.141780
Ted Stevens Anchorage International  2246.530036
Daniel K Inouye International        2211.857407
Took 169.8935534954071 s

এই ফলাফলগুলি অর্থপূর্ণ, যেহেতু তালিকাভুক্ত বিমানবন্দরগুলি যথাক্রমে আমেরিকান সামোয়া, গুয়াম, পুয়ের্তো রিকো, আলাস্কা এবং হাওয়াইতে রয়েছে৷ এগুলি মার্কিন যুক্তরাষ্ট্রের বাইরের সমস্ত অবস্থান যেখানে কেউ দীর্ঘ গড় ফ্লাইটের দূরত্ব আশা করতে পারে।

এখানে সমস্যাটি ফলাফল নয় – যা বৈধ – তবে কার্যকর করার সময়: প্রায় তিন মিনিট! যদিও তিন মিনিট একবারের জন্য সহনীয় হতে পারে, বিকাশের সময় এটি একটি উত্পাদনশীলতা হত্যাকারী হয়ে ওঠে। এটিকে একটি দীর্ঘ ডেটা পাইপলাইনের অংশ হিসাবে কল্পনা করুন। প্রতিবার একটি প্যারামিটার পরিবর্তন করা হয়, একটি বাগ সংশোধন করা হয়, বা একটি ঘর পুনরায় আঁকা হয়, আপনি প্রোগ্রাম চালানোর সময় নিষ্ক্রিয় বসে থাকতে বাধ্য হন৷ সেই ঘর্ষণটি আপনার প্রবাহকে ভেঙে দেয় এবং একটি দ্রুত বিশ্লেষণকে পুরো বিকেলের বিষয়ে পরিণত করে।

এখন দেখা যাক কিভাবে py-spy কোন লাইনগুলি কতটা সময় নিচ্ছে তা খুঁজে পেতে এটি আমাদের সাহায্য করতে পারে।

Py-স্পাই কি?

কি বুঝতে হবে py-spy এটি একজন কী করছে এবং এটি ব্যবহার করে কী কী সুবিধা রয়েছে তা তুলনা করতে সহায়তা করে। py-spy বিল্ট-ইন পাইথন প্রোফাইলারের জন্য cProfile.

  • cProfile: এটা একটা ট্রেসিং প্রোফাইলারপ্রতিটি ফাংশন কলে একটি স্টপওয়াচের মতো কাজ করে। প্রতিটি ফাংশন কল এবং রিটার্নের মধ্যে সময় পরিমাপ করা হয় এবং রিপোর্ট করা হয়। যদিও অত্যন্ত নির্ভুল, এটি উল্লেখযোগ্য ওভারহেড যোগ করে, কারণ প্রোফাইলারকে ক্রমাগত থামতে হবে এবং ডেটা রেকর্ড করতে হবে, যা স্ক্রিপ্টটিকে উল্লেখযোগ্যভাবে ধীর করে দিতে পারে।
  • py-spy: এটা একটা নমুনা প্রোফাইলারএটি একটি উচ্চ গতির ক্যামেরার মতো কাজ করে যা একই সাথে পুরো প্রোগ্রামটি দেখে। py-spy চলমান পাইথন স্ক্রিপ্টের সম্পূর্ণ বাইরে বসে এবং প্রোগ্রামের অবস্থার উচ্চ-ফ্রিকোয়েন্সি স্ন্যাপশট নেয়। কোডের কোন লাইনটি চলছে এবং কোন ফাংশনটি এটিকে বলে তা দেখতে এটি সম্পূর্ণ “কল স্ট্যাক”, উপরের স্তর পর্যন্ত দেখায়।

py-spy চলছে

চালানো py-spy পাইথন স্ক্রিপ্টে, py-spy লাইব্রেরি একটি পাইথন পরিবেশে ইনস্টল করা আবশ্যক।

pip install py-spy

একবার py-spy লাইব্রেরি ইনস্টল হয়ে গেলে, টার্মিনালে নিম্নলিখিত কমান্ডগুলি চালিয়ে আমাদের স্ক্রিপ্টটি প্রোফাইল করা যেতে পারে:

py-spy record -o profile.svg -r 100 -- python main.py

এই কমান্ডের প্রতিটি অংশ আসলে কী করছে তা এখানে:

  • py-spy: টুল কল করে।
  • record: এই বলে py-spy এটির “রেকর্ড” মোড ব্যবহার করার জন্য, যা প্রোগ্রামটি চলমান থাকার সময় ক্রমাগত নিরীক্ষণ করবে এবং ডেটা সংরক্ষণ করবে।
  • -o profile.svg: এটি আউটপুট ফাইলের নাম এবং বিন্যাস নির্দিষ্ট করে, এটিকে একটি SVG ফাইল হিসাবে ফলাফল আউটপুট করতে বলে। profile.svg.
  • -r 100: এটি প্রতি সেকেন্ডে 100 বার সেট করে নমুনার হার নির্দিষ্ট করে৷ এর মানে হল py-spy প্রোগ্রাম কি করছে প্রতি সেকেন্ডে 100 বার চেক করবে।
  • --: এটা আলাদা করে py-spy পাইথন স্ক্রিপ্ট কমান্ড থেকে কমান্ড। এটা বলে py-spy এই পতাকা অনুসরণ করে যে সবকিছু চালানোর আদেশ, একটি যুক্তি নয় py-spy স্ব.
  • python main.py: এটি প্রোফাইলিংয়ের জন্য পাইথন স্ক্রিপ্ট চালানোর কমান্ড py-spyএই সমস্যা চলমান main.py.

মন্তব্য করুন: লিনাক্সে চললে, sudo চালানোর বিশেষাধিকার প্রায়ই একটি প্রয়োজন হয় py-spyনিরাপত্তার কারণে।

এই কমান্ড চালানোর পরে, একটি আউটপুট ফাইল profile.svg প্রদর্শিত হবে যা আমাদের গভীরভাবে জানতে দেবে কোডের কোন অংশগুলি সবচেয়ে বেশি সময় নিচ্ছে।

py-স্পাই আউটপুট

কেন আমার কোড এত ধীর? py-spy ডেটা সায়েন্সের দিকে পাইথন প্রোফাইলিংয়ের একটি গাইড
py-spy থেকে Icicle গ্রাফ আউটপুট

খোলার আউটপুট profile.svg সেই দৃশ্য প্রকাশ করে py-spy আমাদের প্রোগ্রামটি কোডের বিভিন্ন অংশে কতটা সময় ব্যয় করা হয়েছে তা পরিমাপ করার জন্য ডিজাইন করা হয়েছে। এটি একটি হিসাবে পরিচিত হয় বরফ গ্রাফ (বা কখনও কখনও ক শিখা গ্রাফ যদি y-অক্ষ উল্টানো হয়) এবং এইভাবে ব্যাখ্যা করা হয়:

  • বার: প্রতিটি রঙিন বার একটি নির্দিষ্ট ফাংশনকে প্রতিনিধিত্ব করে যা প্রোগ্রামটি কার্যকর করার সময় বলা হয়েছিল।
  • এক্স-অক্ষ (জনসংখ্যা): অনুভূমিক অক্ষ প্রোফাইলিংয়ের সময় নেওয়া সমস্ত নমুনার সংগ্রহের প্রতিনিধিত্ব করে। এগুলিকে গোষ্ঠীভুক্ত করা হয়েছে যাতে একটি নির্দিষ্ট বারের প্রস্থ সেই বারের দ্বারা উপস্থাপিত ফাংশনে প্রোগ্রামটির মোট নমুনার অনুপাতকে উপস্থাপন করে। মন্তব্য: এটা না একটি সময়রেখা; অর্ডারটি কখন ফাংশনটি কল করা হয়েছিল তা নির্দেশ করে না, শুধুমাত্র মোট সময় ব্যয় করা হয়েছে৷
  • Y-অক্ষ (স্ট্যাকের গভীরতা): উল্লম্ব অক্ষ কল স্ট্যাকের প্রতিনিধিত্ব করে। “সমস্ত” লেবেলযুক্ত শীর্ষ বারটি সমগ্র প্রোগ্রামের প্রতিনিধিত্ব করে এবং এর নীচের বারগুলি “সমস্ত” থেকে বলা ফাংশনগুলিকে প্রতিনিধিত্ব করে। এটি পুনরাবৃত্তভাবে চলতে থাকে এবং প্রতিবার ফাংশনগুলিতে বিভক্ত হয় যা এটি কার্যকর করার সময় বলা হয়েছিল। নীচের বারটি সেই ফাংশনটি দেখায় যা আসলে সিপিইউতে চলছিল যখন নমুনা নেওয়া হয়েছিল।

গ্রাফের সাথে মিথস্ক্রিয়া করা

উপরের চিত্রটি স্থির, বাস্তব .svg ফাইল দ্বারা উত্পন্ন py-spy সম্পূর্ণ ইন্টারেক্টিভ। আপনি যখন এটি একটি ওয়েব ব্রাউজারে খুলুন, আপনি করতে পারেন:

  • অনুসন্ধান (Ctrl+F): নির্দিষ্ট ফাংশনগুলিকে হাইলাইট করুন যাতে তারা স্ট্যাকের মধ্যে কোথায় উপস্থিত হয় তা দেখতে।
  • জুম: যে নির্দিষ্ট ফাংশন এবং এর বাচ্চাদের জুম করতে যেকোন বারে ক্লিক করুন, আপনাকে কল স্ট্যাকের জটিল অংশগুলিকে বিচ্ছিন্ন করার অনুমতি দেয়।
  • হোভার করতে: যেকোন বারের উপর ঘোরাফেরা করলে নির্দিষ্ট ফাংশনের নাম, ফাইলের পথ, লাইন নম্বর এবং এটি দ্বারা ব্যবহৃত সময়ের সঠিক শতাংশ দেখায়।

একটি বরফ গ্রাফ পড়ার জন্য সবচেয়ে গুরুত্বপূর্ণ নিয়ম হল এই: বার যত চওড়া হবে, তত বেশি বার কাজ করা হবে. যদি একটি ফাংশন বার গ্রাফের প্রস্থের 50% জুড়ে থাকে, তাহলে এর মানে হল যে প্রোগ্রামটি মোট রানটাইমের 50% জন্য সেই ফাংশনটি কার্যকর করার জন্য কাজ করছে।

রোগ নির্ণয়

উপরের আইসিকল গ্রাফ থেকে, আমরা দেখতে পাচ্ছি যে বারটি পান্ডাদের প্রতিনিধিত্ব করে iterrows() ফাংশন বেশ ব্যাপক. দেখার সময় সেই দণ্ডের উপর হোভার করুন profile.svg ফাইলটি এই ফাংশনের জন্য সঠিক অনুপাতটি দেখায় 68.36%. তাই রানটাইমের 2/3 এরও বেশি সময় ব্যয় করা হয়েছিল iterrows() উদযাপন। এই সীমাবদ্ধতা intuitively বোধগম্য, হিসাবে iterrows() লুপের প্রতিটি সারির জন্য একটি পান্ডাস চেইন অবজেক্ট তৈরি করে, যার ফলে বিশাল ওভারহেড হয়। এটি স্ক্রিপ্টের রানটাইম চেষ্টা এবং অপ্টিমাইজ করার একটি স্পষ্ট লক্ষ্য দেখায়।

স্ক্রিপ্টের অভিযোজন

যা শেখা হয়েছিল তার উপর ভিত্তি করে এই স্ক্রিপ্টটিকে মানিয়ে নেওয়ার সবচেয়ে সুস্পষ্ট পথ py-spy ব্যবহার বন্ধ করতে iterrows() সেই হ্যাভারসাইন দূরত্ব গণনা করতে প্রতিটি লাইনের উপর লুপ করা। পরিবর্তে, এটিকে NumPy ব্যবহার করে একটি ভেক্টরাইজড গণনা দিয়ে প্রতিস্থাপিত করা উচিত যা শুধুমাত্র একটি ফাংশন কলের মাধ্যমে প্রতিটি সারির জন্য গণনা সম্পাদন করবে। সুতরাং যে পরিবর্তনগুলি করতে হবে তা হল:

  • পুনরায় লিখুন haversine() ভেক্টরাইজড এবং দক্ষ C-স্তরের NumPy অপারেশনগুলি ব্যবহার করার জন্য ফাংশন যা একটি সময়ে স্থানাঙ্কের সেটের পরিবর্তে সমগ্র অ্যারেগুলিকে পাস করার অনুমতি দেয়।
  • প্রতিস্থাপন করুন iterrows() এই নতুন ভেক্টরাইজড একটি একক কল সঙ্গে লুপ haversine() উদযাপন।
import pandas as pd  
import numpy as np  
import time  
  
  
def haversine(lat_1, lon_1, lat_2, lon_2):  
    """Calculate the Haversine Distance between two latitude and longitude points"""  
    lat_1_rad = np.radians(lat_1)  
    lon_1_rad = np.radians(lon_1)  
    lat_2_rad = np.radians(lat_2)  
    lon_2_rad = np.radians(lon_2)  
  
    delta_lat = lat_2_rad - lat_1_rad  
    delta_lon = lon_2_rad - lon_1_rad  
  
    R = 6371  # Radius of the earth in km  
  
    return 2*R*np.asin(np.sqrt(np.sin(delta_lat/2)**2 + np.cos(lat_1_rad)*np.cos(lat_2_rad)*(np.sin(delta_lon/2))**2))  
  
  
if __name__ == '__main__':  
    # Load in flight data to a dataframe  
    flight_data_file = r"./data/2025_flight_data.csv"  
    flights_df = pd.read_csv(flight_data_file)  
  
    # Start timer to see how long analysis takes  
    start = time.time()  
  
    # Calculate the haversine distance between each flight's start and end airport  
    flights_df["Distance"] = haversine(lat_1=flights_df["LATITUDE_ORIGIN"],  
                                       lon_1=flights_df["LONGITUDE_ORIGIN"],  
                                       lat_2=flights_df["LATITUDE_DEST"],  
                                       lon_2=flights_df["LONGITUDE_DEST"])  
  
    # Get result by grouping by origin airport, taking the average flight distance and      printing the top 5  
    result = (  
        flights_df  
        .groupby('DISPLAY_AIRPORT_NAME_ORIGIN').agg(avg_dist=('Distance', 'mean'))  
        .sort_values('avg_dist', ascending=False)  
    )  
  
    print(result.head(5))  
  
    # End timer and print analysis time  
    end = time.time()  
    print(f"Took {end - start} s")

এই কোড চালানো নিম্নলিখিত আউটপুট দেয়:

                                        avg_dist
DISPLAY_AIRPORT_NAME_ORIGIN                     
Pago Pago International              4202.493567
Guam International                   3142.363005
Luis Munoz Marin International       2386.141780
Ted Stevens Anchorage International  2246.530036
Daniel K Inouye International        2211.857407
Took 0.5649983882904053 s

এই ফলাফলগুলি কোডটি অপ্টিমাইজ করার আগে ফলাফলের মতো, কিন্তু প্রক্রিয়াটির পরিবর্তে প্রায় তিন মিনিট সময় নেয়, আধা সেকেন্ডেরও বেশি সময় লেগেছে!

সামনে তাকিয়ে

আপনি যদি এটি ভবিষ্যতে থেকে পড়ছেন (2026 সালের শেষের দিকে বা তার পরে), আপনি পাইথন 3.15 বা নতুন চালাচ্ছেন কিনা তা পরীক্ষা করুন। Python 3.15 আদর্শ লাইব্রেরিতে একটি নেটিভ স্যাম্পলিং প্রোফাইলার প্রবর্তন করবে বলে আশা করা হচ্ছে, অনুরূপ কার্যকারিতা প্রদান করে। py-spy বাহ্যিক ইনস্টলেশনের প্রয়োজন ছাড়াই। Python 3.14 বা তার নিচের যে কারো জন্য py-spy গোল্ড স্ট্যান্ডার্ড অবশেষ.

এই নিবন্ধটি ডেটা সায়েন্সে একটি সাধারণ হতাশা মোকাবেলা করার জন্য একটি টুলের অনুসন্ধান করে – একটি স্ক্রিপ্ট যা প্রত্যাশিতভাবে কাজ করে, কিন্তু অদক্ষভাবে লেখা হয় এবং চলতে অনেক সময় লাগে। হ্যাভারসাইন দূরত্ব অনুযায়ী কোন মার্কিন প্রস্থান বিমানবন্দরের দীর্ঘতম গড় ফ্লাইট দূরত্ব রয়েছে তা খুঁজে বের করার জন্য একটি উদাহরণ স্ক্রিপ্ট দেওয়া হয়েছিল। এই স্ক্রিপ্টটি প্রত্যাশিত হিসাবে কাজ করেছে, কিন্তু এটি চালানোর জন্য প্রায় তিন মিনিট সময় নিয়েছে।

ব্যবহার করে py-spy পাইথন প্রোফাইলার, আমরা শিখতে পেরেছি যে অদক্ষতার কারণ ছিল ব্যবহার iterrows() উদযাপন। প্রতিস্থাপন দ্বারা iterrows() হ্যাভারসাইন দূরত্বের আরও দক্ষ ভেক্টরাইজড গণনার সাথে, রানটাইমটি অপ্টিমাইজ করা হয়েছিল, এটিকে তিন মিনিট থেকে মাত্র অর্ধ সেকেন্ডে কমিয়েছে।

এই নিবন্ধটির কোডের জন্য আমার GitHub সংগ্রহস্থল দেখুন, এতে BTS থেকে কাঁচা ডেটার প্রিপ্রসেসিংও অন্তর্ভুক্ত রয়েছে।

পড়ার জন্য আপনাকে ধন্যবাদ!

তথ্য উৎস

পরিবহন পরিসংখ্যান ব্যুরোর (বিটিএস) ডেটা মার্কিন যুক্তরাষ্ট্রের ফেডারেল সরকারের কাজ এবং এর অধীনে সর্বজনীন ডোমেনে রয়েছে 17 ইউএসসি § 105. এটি কপিরাইট বিধিনিষেধ ছাড়াই ব্যবহার, ভাগ এবং মানিয়ে নিতে বিনামূল্যে৷

Leave a Reply

Your email address will not be published. Required fields are marked *