অ্যালগোরিদম সীমিত করার জন্য একটি ভাল হার কী?


155

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

উত্তর:


231

এখানে সর্বাধিক সহজ অ্যালগরিদম , আপনি খুব দ্রুত আগত হলে কেবল বার্তা ফেলে দিতে চান (তাদের সারি সারি করার পরিবর্তে, যা বোঝায় কারণ সারিটি নির্বিচারে বড় হতে পারে):

rate = 5.0; // unit: messages
per  = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds

when (message_received):
  current = now();
  time_passed = current - last_check;
  last_check = current;
  allowance += time_passed * (rate / per);
  if (allowance > rate):
    allowance = rate; // throttle
  if (allowance < 1.0):
    discard_message();
  else:
    forward_message();
    allowance -= 1.0;

এই সমাধানে কোনও ডেটাস্ট্রাকচার, টাইমার ইত্যাদি নেই এবং এটি পরিষ্কারভাবে কাজ করে :) এটি দেখতে, 'ভাতা' সেকেন্ডে সর্বোচ্চ 5/8 ইউনিট গতিবেগেই বৃদ্ধি পায়, অর্থাৎ আট সেকেন্ডে সর্বোচ্চ পাঁচটি ইউনিট। ফরোয়ার্ড হওয়া প্রতিটি বার্তা একটি ইউনিটকে হ্রাস করে, তাই আপনি প্রতি আট সেকেন্ডে পাঁচটির বেশি বার্তা পাঠাতে পারবেন না।

নোট করুন যে rateএকটি পূর্ণসংখ্যা হওয়া উচিত, অর্থাত্ শূন্যহীন দশমিক অংশ ব্যতীত, বা অ্যালগোরিদম সঠিকভাবে কাজ করবে না (আসল হারটি হবে না rate/per)। উদাহরণস্বরূপ rate=0.5; per=1.0;কাজ করে না কারণ allowanceকখনই 1.0 এ বাড়তে পারে না। তবে rate=1.0; per=2.0;ঠিক আছে।


4
এটি উল্লেখ করার মতোও যে 'সময়_পাস্ত' এর মাত্রা এবং স্কেল অবশ্যই 'প্রতি', যেমন সেকেন্ডের সমান হতে হবে।
স্কাফম্যান

2
হাই স্কাফম্যান, অভিনন্দনের জন্য ধন্যবাদ --- আমি এটি আমার আস্তিনের বাইরে ফেলে দিলাম তবে ৯৯.৯% সম্ভাব্যতার সাথে কেউ এর আগে একই সমাধান
পেয়েছে

52
এটি একটি স্ট্যান্ডার্ড অ্যালগরিদম — এটি একটি টোকেন বালতি, সারি ছাড়াই। বালতি হয় allowance। বালতির আকার rateallowance += …লাইন যে টোকেন একটি যোগ করার একটি অপ্টিমাইজেশান হল হার ÷ প্রতি সেকেন্ড।
ডারোবার্ট

5
@ zwirbeltier আপনি উপরে যা লিখেছেন তা সত্য নয়। 'ভাতা' সর্বদা 'রেট' দ্বারা ক্যাপড থাকে ("// থ্রোটল" লাইনটি দেখুন) সুতরাং এটি কেবল কোনও নির্দিষ্ট সময়ে অর্থাত্ 'রেট' বার্তাগুলি
বিস্ফোরনের অনুমতি দেবে

7
এটি ভাল, তবে হারটি ছাড়িয়ে যেতে পারে। আসুন 0 সময় আপনি 5 বার্তা ফরোয়ার্ড করুন, তারপরে N = 1, 2, এর জন্য N * (8/5) এর জন্য ... আপনি অন্য বার্তাটি পাঠাতে পারবেন, ফলে 8 টি দ্বিতীয় সময়কালে 5 টিরও বেশি বার্তা যাবে
মাইন্ডভাইরাস

47

আপনার ক্রিয়াকলাপটি প্রশংসনীয় হওয়ার আগে @ রেটলিমিটেড (রেটপারসেক) ব্যবহার করুন

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

আপনার ক্ষেত্রে, আপনি যদি প্রতি 8 সেকেন্ডে সর্বাধিক 5 বার্তা চান তবে আপনার সেন্ডকুইকিউ ফাংশনের আগে @ রেটলিমিটেড (0.625) ব্যবহার করুন।

import time

def RateLimited(maxPerSecond):
    minInterval = 1.0 / float(maxPerSecond)
    def decorate(func):
        lastTimeCalled = [0.0]
        def rateLimitedFunction(*args,**kargs):
            elapsed = time.clock() - lastTimeCalled[0]
            leftToWait = minInterval - elapsed
            if leftToWait>0:
                time.sleep(leftToWait)
            ret = func(*args,**kargs)
            lastTimeCalled[0] = time.clock()
            return ret
        return rateLimitedFunction
    return decorate

@RateLimited(2)  # 2 per second at most
def PrintNumber(num):
    print num

if __name__ == "__main__":
    print "This should print 1,2,3... at about 2 per second."
    for i in range(1,100):
        PrintNumber(i)

আমি এই উদ্দেশ্যে একটি সজ্জা ব্যবহার করার ধারণা পছন্দ করি। লাস্টটাইম কেন একটি তালিকা বলা হয়েছে? এছাড়াও, আমি সন্দেহ করি যখন একাধিক থ্রেড একই রেটলিমিটেড ফাংশনটি কল করে ...
স্টিফান202

8
এটি একটি তালিকা কারণ কোনও ধরণের বন্ধনের মাধ্যমে ধরা পড়লে ফ্লোটের মতো সাধারণ ধরণের ধ্রুবক থাকে। এটিকে একটি তালিকা তৈরি করে, তালিকাটি ধ্রুবক, তবে এর সামগ্রীগুলি নেই। হ্যাঁ, এটি থ্রেড-নিরাপদ নয় তবে এটি সহজেই লকগুলির সাথে ঠিক করা যেতে পারে।
কার্লোস এ। ইবাররা

time.clock()আমার সিস্টেমে পর্যাপ্ত রেজোলিউশন নেই, তাই আমি কোডটি মানিয়ে নিয়েছি এবং ব্যবহার করতে পরিবর্তন করেছিtime.time()
মিত্রাবিন

3
রেট সীমাবদ্ধতার জন্য, আপনি অবশ্যই ব্যবহার করতে চান না time.clock(), যা সিপিইউয়ের সময় কেটে ফেলেছে measures সিপিইউ সময় "প্রকৃত" সময়ের চেয়ে অনেক দ্রুত বা অনেক ধীর চলতে পারে। time.time()পরিবর্তে আপনি ব্যবহার করতে চান , যা দেয়ালের সময়কে পরিমাপ করে ("প্রকৃত" সময়)।
জন বুদ্ধিমান

1
রিয়েল প্রোডাকশন সিস্টেমের জন্য বিটিডাব্লু: একটি স্লিপ () কলের সাথে সীমাবদ্ধ হার নির্ধারণ কার্যকর ধারণা নাও হতে পারে কারণ এটি থ্রেডটি ব্লক করতে চলেছে এবং অন্য ক্লায়েন্টকে এটি ব্যবহার করা থেকে বিরত রাখবে।
মারেশ

28

একটি টোকেন বালতি প্রয়োগ করা মোটামুটি সহজ।

5 টোকেন দিয়ে বালতি দিয়ে শুরু করুন।

প্রতি 5/8 সেকেন্ড: বালতিতে যদি 5 টোকেনের চেয়ে কম থাকে তবে একটি যুক্ত করুন।

প্রতিবার আপনি বার্তা পাঠাতে চান: বালতিটিতে যদি ≥1 টোকেন থাকে তবে একটি টোকেন বের করে বার্তাটি প্রেরণ করুন। অন্যথায়, অপেক্ষা করুন / বার্তাটি / যাই হোক না কেন ফেলে দিন।

(স্পষ্টতই, আসল কোডে আপনি বাস্তব টোকেনের পরিবর্তে একটি পূর্ণসংখ্যার কাউন্টার ব্যবহার করতে পারেন এবং টাইমস্ট্যাম্পগুলি সংরক্ষণ করে আপনি প্রতি 5 / 8s পদক্ষেপটি অপ্টিমাইজ করতে পারেন)


আবার প্রশ্নটি পড়া, যদি হারের সীমাটি প্রতি 8 সেকেন্ডে পুরোপুরি পুনরায় সেট করা হয় তবে এখানে একটি পরিবর্তন আছে:

একটি টাইমস্ট্যাম্প দিয়ে শুরু করুন, last_sendঅনেক আগে (যেমন, যুগের আগে)) এছাড়াও, একই 5-টোকেন বালতি দিয়ে শুরু করুন।

প্রতি 5/8 সেকেন্ডের নিয়মটি স্ট্রাইক করুন।

প্রতিবার আপনি বার্তা প্রেরণ করুন: প্রথমে, last_send8 সেকেন্ড আগে পরীক্ষা করে দেখুন । যদি তা হয় তবে বালতিটি পূরণ করুন (এটি 5 টোকেনে সেট করুন)। দ্বিতীয়ত, যদি বালতিতে টোকেন থাকে তবে বার্তাটি প্রেরণ করুন (অন্যথায়, ড্রপ / ওয়েট / ইত্যাদি)। তৃতীয়, last_sendএখন সেট করুন।

এটি সেই দৃশ্যের জন্য কাজ করা উচিত।


আমি আসলে এই জাতীয় কৌশল ব্যবহার করে একটি আইআরসি বট লিখেছি (প্রথম পদ্ধতির)। এটি পার্লে রয়েছে, পাইথন নয়, তবে এখানে কিছু উদাহরণ দেওয়ার জন্য এখানে রয়েছে:

এখানে প্রথম অংশটি বালতিতে টোকেন যুক্ত করে পরিচালনা করে। আপনি সময়ের উপর ভিত্তি করে টোকেন যুক্ত করার অপ্টিমাইজেশন দেখতে পারেন (দ্বিতীয় থেকে শেষ লাইনের) এবং তারপরে সর্বশেষ লাইনে ক্ল্যাম্পস বালতি সামগ্রী সর্বাধিক (MESSAGE_BURST)

    my $start_time = time;
    ...
    # Bucket handling
    my $bucket = $conn->{fujiko_limit_bucket};
    my $lasttx = $conn->{fujiko_limit_lasttx};
    $bucket += ($start_time-$lasttx)/MESSAGE_INTERVAL;
    ($bucket <= MESSAGE_BURST) or $bucket = MESSAGE_BURST;

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

    # Queue handling. Start with the ultimate queue.
    my $queues = $conn->{fujiko_queues};
    foreach my $entry (@{$queues->[PRIORITY_ULTIMATE]}) {
            # Ultimate is special. We run ultimate no matter what. Even if
            # it sends the bucket negative.
            --$bucket;
            $entry->{code}(@{$entry->{args}});
    }
    $queues->[PRIORITY_ULTIMATE] = [];

এটি প্রথম সারি, যা চালানো যাই হোক না কেন। এমনকি যদি এটি আমাদের সংযোগ বন্যার জন্য মারা যায়। সার্ভারের পিংয়ের প্রতিক্রিয়া জানানোর মতো অত্যন্ত গুরুত্বপূর্ণ কাজের জন্য ব্যবহৃত হয়। পরবর্তী, বাকি সারিতে:

    # Continue to the other queues, in order of priority.
    QRUN: for (my $pri = PRIORITY_HIGH; $pri >= PRIORITY_JUNK; --$pri) {
            my $queue = $queues->[$pri];
            while (scalar(@$queue)) {
                    if ($bucket < 1) {
                            # continue later.
                            $need_more_time = 1;
                            last QRUN;
                    } else {
                            --$bucket;
                            my $entry = shift @$queue;
                            $entry->{code}(@{$entry->{args}});
                    }
            }
    }

অবশেষে, বালতির স্থিতি আবার $ কান ডেটা স্ট্রাকচারে সংরক্ষণ করা হয় (আসলে পদ্ধতিতে কিছুটা পরে; এটি আরও কাজ করে কত শীঘ্রই এটি প্রথমে গণনা করে)

    # Save status.
    $conn->{fujiko_limit_bucket} = $bucket;
    $conn->{fujiko_limit_lasttx} = $start_time;

আপনি দেখতে পাচ্ছেন, আসল বালতি হ্যান্ডলিং কোডটি খুব ছোট - প্রায় চারটি লাইন। কোডের বাকি অংশটি হ'ল অগ্রাধিকারের সারি হ্যান্ডলিং। বটের অগ্রাধিকারের কাত রয়েছে যাতে উদাহরণস্বরূপ, কেউ এটির সাথে চ্যাট করে এটি এর গুরুত্বপূর্ণ কিক / নিষেধাজ্ঞার দায়িত্ব পালন থেকে আটকাতে পারে না।


আমি কি কিছু নিখোঁজ করছি ... দেখে মনে হচ্ছে এটি প্রথম 5
শীতল 42

@ চিলস 42: হ্যাঁ, আমি প্রশ্নটি ভুল পড়েছি ... উত্তরটির দ্বিতীয়ার্ধটি দেখুন।
ডারোবার্ট

@ ছিলস: শেষ_সেন্ডারটি যদি 8 8 সেকেন্ড হয় তবে আপনি বালতিতে কোনও টোকেন যুক্ত করবেন না। যদি আপনার বালতিতে টোকেন থাকে তবে আপনি বার্তাটি পাঠাতে পারেন; অন্যথায় আপনি পারবেন না (আপনি ইতিমধ্যে শেষ 8 সেকেন্ডে 5 বার্তা প্রেরণ করেছেন)
ডারোবার্ট

3
আমি এটির প্রশংসা করব যদি লোকেরা এটিকে নিচে নামিয়ে দেয় তবে দয়া করে ব্যাখ্যা করুন কেন ... আপনি যে কোনও সমস্যা দেখেন তা আমি ঠিক করতে চাই, তবে প্রতিক্রিয়া ছাড়াই এটি করা কঠিন!
ডারোবার্ট

10

বার্তা প্রেরণ না হওয়া অবধি প্রক্রিয়াকরণ অবরুদ্ধ করতে, এইভাবে আরও বার্তাগুলি সন্ধান করার জন্য, অ্যান্টির সুন্দর সমাধানটিও এভাবে পরিবর্তন করা যেতে পারে:

rate = 5.0; // unit: messages
per  = 8.0; // unit: seconds
allowance = rate; // unit: messages
last_check = now(); // floating-point, e.g. usec accuracy. Unit: seconds

when (message_received):
  current = now();
  time_passed = current - last_check;
  last_check = current;
  allowance += time_passed * (rate / per);
  if (allowance > rate):
    allowance = rate; // throttle
  if (allowance < 1.0):
    time.sleep( (1-allowance) * (per/rate))
    forward_message();
    allowance = 0.0;
  else:
    forward_message();
    allowance -= 1.0;

বার্তাটি প্রেরণের জন্য পর্যাপ্ত ভাতা না পাওয়া পর্যন্ত এটি অপেক্ষা করে। দ্বিগুণ হারের সাথে শুরু না করার জন্য, ভাতা 0 দিয়ে শুরুও করা যেতে পারে।


5
আপনি যখন ঘুমাবেন (1-allowance) * (per/rate), আপনার একই পরিমাণটি যুক্ত করতে হবে last_check
অলপ

2

শেষ পাঁচটি লাইন প্রেরণ করা সময় রাখুন। পঞ্চম-সর্বাধিক সাম্প্রতিক বার্তা (এটি উপস্থিত থাকলে) অবধি কমপক্ষে 8 সেকেন্ড অবধি অবধি অবধি অবধি সজ্জিত থাকুন (শেষ বারের সাথে অ্যারেস্ট হিসাবে ফিফটি):

now = time.time()
if len(last_five) == 0 or (now - last_five[-1]) >= 8.0:
    last_five.insert(0, now)
    send_message(msg)
if len(last_five) > 5:
    last_five.pop()

আপনি যেহেতু এটি সংশোধন করেছেন আমি তা করছি না।
পেস্টো

আপনি পাঁচবারের স্ট্যাম্প সংরক্ষণ করছেন এবং বারবার এগুলিকে মেমরির মাধ্যমে স্থানান্তরিত করছেন (বা লিঙ্কযুক্ত তালিকার ক্রিয়াকলাপ করছেন)। আমি একটি পূর্ণসংখ্যার কাউন্টার এবং একটি টাইমস্ট্যাম্প সংরক্ষণ করছি। এবং শুধুমাত্র পাটিগণিত এবং নির্ধারণ।
ডার্বার্ট

2
5 টি লাইন প্রেরণ করার চেষ্টা করলে খনিটি আরও ভালভাবে কাজ করবে তবে সময়কালীন সময়ে আরও 3 টি অনুমোদিত। আপনার প্রথমটি তিনটি প্রেরণ করার অনুমতি দেবে এবং 4 এবং 5 প্রেরণের আগে 8 সেকেন্ড অপেক্ষা করতে বাধ্য করবে খনিটি চতুর্থ- এবং পঞ্চম-সর্বাধিক সাম্প্রতিক লাইনের পরে 8 সেকেন্ডে 4 এবং 5 পাঠানোর অনুমতি দেবে।
পেস্তো

1
তবে বিষয়টিতে, 5 দৈর্ঘ্যের একটি বিজ্ঞপ্তিযুক্ত লিঙ্কযুক্ত তালিকা ব্যবহার করে পঞ্চম-সর্বাধিক প্রেরণের দিকে ইশারা করে, এটি নতুন প্রেরণে ওভাররাইট করে এবং পয়েন্টারটিকে এগিয়ে নিয়ে যাওয়ার মাধ্যমে পারফরম্যান্সটি উন্নত করা যায়।
পেস্টো

রেট সীমাবদ্ধ গতির সাথে একটি আইর্স বট কোনও সমস্যা নয়। আমি তালিকার সমাধানটিকে আরও বেশি পঠনযোগ্য বলে পছন্দ করি। যে বালতি উত্তরটি দেওয়া হয়েছে তা সংশোধনীর কারণে বিভ্রান্তিকর, তবে এটিতে কোনও ভুল নেই।
ঝেরিকো

2

একটি সমাধান হ'ল প্রতিটি সারি আইটেমের সাথে টাইমস্ট্যাম্প সংযুক্ত করা এবং 8 সেকেন্ড পেরিয়ে যাওয়ার পরে আইটেমটি বাতিল করা। প্রতিবার কাতারে যুক্ত হওয়ার পরে আপনি এই চেকটি সম্পাদন করতে পারেন।

এটি কেবলমাত্র যদি আপনি সারির আকার 5 এর মধ্যে সীমাবদ্ধ করেন এবং সারিটি পূর্ণ থাকে তবে কোনও সংযোজন ফেলে দিন যদি তা কার্যকর হয়।


1

যদি এখনও কেউ আগ্রহী হন, আমি আইপি প্রতি অনুরোধের হার সীমাবদ্ধ করতে একটি সময়সী এলআরইউ কী মান স্টোরেজের সাথে একযোগে এই সাধারণ কলযোগ্য ক্লাসটি ব্যবহার করি। একটি ডিউক ব্যবহার করে তবে পরিবর্তে তালিকার সাথে আবার ব্যবহার করতে আবার লিখতে পারে।

from collections import deque
import time


class RateLimiter:
    def __init__(self, maxRate=5, timeUnit=1):
        self.timeUnit = timeUnit
        self.deque = deque(maxlen=maxRate)

    def __call__(self):
        if self.deque.maxlen == len(self.deque):
            cTime = time.time()
            if cTime - self.deque[0] > self.timeUnit:
                self.deque.append(cTime)
                return False
            else:
                return True
        self.deque.append(time.time())
        return False

r = RateLimiter()
for i in range(0,100):
    time.sleep(0.1)
    print(i, "block" if r() else "pass")

1

গৃহীত উত্তর থেকে একটি কোডের একটি অজগর বাস্তবায়ন।

import time

class Object(object):
    pass

def get_throttler(rate, per):
    scope = Object()
    scope.allowance = rate
    scope.last_check = time.time()
    def throttler(fn):
        current = time.time()
        time_passed = current - scope.last_check;
        scope.last_check = current;
        scope.allowance = scope.allowance + time_passed * (rate / per)
        if (scope.allowance > rate):
          scope.allowance = rate
        if (scope.allowance < 1):
          pass
        else:
          fn()
          scope.allowance = scope.allowance - 1
    return throttler

আমার পরামর্শ দেওয়া হয়েছে যে আমি আপনাকে আপনার কোডের ব্যবহারের উদাহরণ যুক্ত করার পরামর্শ দিই ।
লুক

0

এটি সম্পর্কে:

long check_time = System.currentTimeMillis();
int msgs_sent_count = 0;

private boolean isRateLimited(int msgs_per_sec) {
    if (System.currentTimeMillis() - check_time > 1000) {
        check_time = System.currentTimeMillis();
        msgs_sent_count = 0;
    }

    if (msgs_sent_count > (msgs_per_sec - 1)) {
        return true;
    } else {
        msgs_sent_count++;
    }

    return false;
}

0

স্কালায় আমার বৈচিত্র দরকার ছিল। এটা এখানে:

case class Limiter[-A, +B](callsPerSecond: (Double, Double), f: A  B) extends (A  B) {

  import Thread.sleep
  private def now = System.currentTimeMillis / 1000.0
  private val (calls, sec) = callsPerSecond
  private var allowance  = 1.0
  private var last = now

  def apply(a: A): B = {
    synchronized {
      val t = now
      val delta_t = t - last
      last = t
      allowance += delta_t * (calls / sec)
      if (allowance > calls)
        allowance = calls
      if (allowance < 1d) {
        sleep(((1 - allowance) * (sec / calls) * 1000d).toLong)
      }
      allowance -= 1
    }
    f(a)
  }

}

এটি কীভাবে ব্যবহার করা যায় তা এখানে:

val f = Limiter((5d, 8d), { 
  _: Unit  
    println(System.currentTimeMillis) 
})
while(true){f(())}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.